mirror of
https://github.com/cc65/cc65.git
synced 2024-11-19 21:32:19 +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').
440 lines
12 KiB
ArmAsm
440 lines
12 KiB
ArmAsm
;
|
|
; Serial driver for the builtin 6551 ACIA of the Commodore 610.
|
|
;
|
|
; Ullrich von Bassewitz, 2003-12-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 "../extzp.inc"
|
|
.include "ser-kernel.inc"
|
|
.include "ser-error.inc"
|
|
.include "cbm610.inc"
|
|
|
|
.macpack module
|
|
|
|
|
|
; ------------------------------------------------------------------------
|
|
; Header. Includes jump table
|
|
|
|
module_header _cbm610_std_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
|
|
|
|
;----------------------------------------------------------------------------
|
|
;
|
|
; Global variables
|
|
;
|
|
|
|
.bss
|
|
RecvHead: .res 1 ; Head of receive buffer
|
|
RecvTail: .res 1 ; Tail of receive buffer
|
|
RecvFreeCnt: .res 1 ; Number of bytes in receive buffer
|
|
SendHead: .res 1 ; Head of send buffer
|
|
SendTail: .res 1 ; Tail of send buffer
|
|
SendFreeCnt: .res 1 ; Number of bytes in send buffer
|
|
|
|
Stopped: .res 1 ; Flow-stopped flag
|
|
RtsOff: .res 1 ;
|
|
|
|
; Send and receive buffers: 256 bytes each
|
|
RecvBuf: .res 256
|
|
SendBuf: .res 256
|
|
|
|
.rodata
|
|
|
|
; Tables used to translate RS232 params into register values
|
|
|
|
BaudTable: ; bit7 = 1 means setting is invalid
|
|
.byte $FF ; SER_BAUD_45_5
|
|
.byte $01 ; SER_BAUD_50
|
|
.byte $02 ; SER_BAUD_75
|
|
.byte $03 ; SER_BAUD_110
|
|
.byte $04 ; SER_BAUD_134_5
|
|
.byte $05 ; SER_BAUD_150
|
|
.byte $06 ; SER_BAUD_300
|
|
.byte $07 ; SER_BAUD_600
|
|
.byte $08 ; SER_BAUD_1200
|
|
.byte $09 ; SER_BAUD_1800
|
|
.byte $0A ; SER_BAUD_2400
|
|
.byte $0B ; SER_BAUD_3600
|
|
.byte $0C ; SER_BAUD_4800
|
|
.byte $0D ; SER_BAUD_7200
|
|
.byte $0E ; SER_BAUD_9600
|
|
.byte $0F ; SER_BAUD_19200
|
|
.byte $FF ; 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.
|
|
;
|
|
; Since we don't have to manage the IRQ vector on the Plus/4, this is actually
|
|
; the same as:
|
|
;
|
|
; UNINSTALL routine. Is called before the driver is removed from memory.
|
|
; Must return an SER_ERR_xx code in a/x.
|
|
;
|
|
; and:
|
|
;
|
|
; CLOSE: Close the port, disable interrupts and flush the buffer. Called
|
|
; without parameters. Must return an error code in a/x.
|
|
;
|
|
|
|
INSTALL:
|
|
UNINSTALL:
|
|
CLOSE:
|
|
|
|
; Deactivate DTR and disable 6551 interrupts
|
|
|
|
lda #%00001010
|
|
jsr write_cmd
|
|
|
|
; Done, return an error code
|
|
|
|
lda #<SER_ERR_OK
|
|
tax ; A is zero
|
|
rts
|
|
|
|
;----------------------------------------------------------------------------
|
|
; 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
|
|
|
|
ldx #0
|
|
stx Stopped
|
|
stx RecvHead
|
|
stx RecvTail
|
|
stx SendHead
|
|
stx SendTail
|
|
dex ; X = 255
|
|
stx RecvFreeCnt
|
|
stx SendFreeCnt
|
|
|
|
; 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
|
|
ldy #ACIA::CTRL
|
|
jsr write
|
|
|
|
; 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
|
|
jsr write_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
|
|
|
|
;----------------------------------------------------------------------------
|
|
; 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
|
|
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
|
|
beq @L3
|
|
cmp #63
|
|
bcc @L3
|
|
lda #$00
|
|
sta Stopped
|
|
lda RtsOff
|
|
ora #%00001000
|
|
jsr write_cmd
|
|
|
|
; Get byte from buffer
|
|
|
|
@L3: ldx RecvHead
|
|
lda RecvBuf,x
|
|
inc RecvHead
|
|
inc RecvFreeCnt
|
|
ldx #$00
|
|
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 #$0F
|
|
sta IndReg
|
|
ldy #ACIA::STATUS
|
|
lda (acia),y
|
|
ldx #0
|
|
sta (ptr1,x)
|
|
lda IndReg
|
|
sta ExecReg
|
|
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: Called from the builtin runtime IRQ handler as a subroutine. All
|
|
; registers are already save, no parameters are passed, but the carry flag
|
|
; is clear on entry. The routine must return with carry set if the interrupt
|
|
; was handled, otherwise with carry clear.
|
|
;
|
|
|
|
IRQ: lda #$0F
|
|
sta IndReg ; Switch to the system bank
|
|
ldy #ACIA::STATUS
|
|
lda (acia),y ; Check ACIA status for receive interrupt
|
|
and #$08
|
|
beq @L9 ; Jump if no ACIA interrupt (carry still clear)
|
|
ldy #ACIA::DATA
|
|
lda (acia),y ; Get byte from ACIA
|
|
ldx RecvFreeCnt ; Check if we have free space left
|
|
beq @L1 ; Jump if no space in receive buffer
|
|
ldy RecvTail ; Load buffer pointer
|
|
sta RecvBuf,y ; Store received byte in buffer
|
|
inc RecvTail ; Increment buffer pointer
|
|
dec RecvFreeCnt ; Decrement free space counter
|
|
cpx #33 ; Check for buffer space low
|
|
bcs @L9 ; Assert flow control if buffer space low
|
|
|
|
; Assert flow control if buffer space too low
|
|
|
|
@L1: lda RtsOff
|
|
ldy #ACIA::CMD
|
|
sta (acia),y
|
|
sta Stopped
|
|
sec ; Interrupt handled
|
|
|
|
; Done, switch back to the execution segment
|
|
|
|
@L9: lda ExecReg
|
|
sta IndReg
|
|
rts
|
|
|
|
;----------------------------------------------------------------------------
|
|
; Try to send a byte. Internal routine. A = TryHard
|
|
|
|
.proc TryToSend
|
|
|
|
sta tmp1 ; Remember tryHard flag
|
|
lda #$0F
|
|
sta IndReg ; Switch to the system bank
|
|
@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: ldy #ACIA::STATUS
|
|
lda (acia),y
|
|
and #$10
|
|
bne @L4
|
|
bit tmp1 ; Keep trying if must try hard
|
|
bmi @L0
|
|
|
|
; Switch back the bank and return
|
|
|
|
@L3: lda ExecReg
|
|
sta IndReg
|
|
rts
|
|
|
|
; Send byte and try again
|
|
|
|
@L4: ldx SendHead
|
|
lda SendBuf,x
|
|
ldy #ACIA::DATA
|
|
sta (acia),y
|
|
inc SendHead
|
|
inc SendFreeCnt
|
|
jmp @L0
|
|
|
|
.endproc
|
|
|
|
|
|
;----------------------------------------------------------------------------
|
|
; Write to the ACIA changing the indirect segment. Offset is in Y, value in A.
|
|
|
|
write_cmd:
|
|
ldy #ACIA::CMD
|
|
write: pha
|
|
lda #$0F
|
|
sta IndReg
|
|
pla
|
|
sta (acia),y
|
|
lda ExecReg
|
|
sta IndReg
|
|
rts
|
|
|