]> git.sur5r.net Git - cc65/commitdiff
Added a serial driver for the builtin 6551 of the Plus/4 (untested, but taken
authorcuz <cuz@b7a2c559-68d2-44c3-8de9-860c34a00d81>
Sun, 14 Dec 2003 17:40:57 +0000 (17:40 +0000)
committercuz <cuz@b7a2c559-68d2-44c3-8de9-860c34a00d81>
Sun, 14 Dec 2003 17:40:57 +0000 (17:40 +0000)
mostly from the C64 version which is tested and works).
Improved interrupt handling (was needed for the driver).

git-svn-id: svn://svn.cc65.org/cc65/trunk@2754 b7a2c559-68d2-44c3-8de9-860c34a00d81

libsrc/plus4/.cvsignore
libsrc/plus4/Makefile
libsrc/plus4/break.s
libsrc/plus4/crt0.s
libsrc/plus4/plus4-stdser.s [new file with mode: 0644]

index 9f998e09188a145b6c18f56a0cb9008b6710fccb..581e5ff62fe59d17a074f16cc087bcb0db5d2299 100644 (file)
@@ -1 +1,4 @@
+*.emd
 *.joy
+*.ser
+*.tgi
index 14e9b6bb6ccff1d4c3bf9c664e24825032b58ea1..cb28697405406f2f1a51c8f6050208b1e0009ba5 100644 (file)
@@ -20,6 +20,9 @@
 %.joy: %.o ../runtime/zeropage.o
        @$(LD) -t module -o $@ $^
 
+%.ser: %.o ../runtime/zeropage.o
+       @$(LD) -t module -o $@ $^
+
 %.tgi: %.o ../runtime/zeropage.o
        @$(LD) -t module -o $@ $^
 
@@ -73,6 +76,8 @@ EMDS =
 
 JOYS = plus4-stdjoy.joy
 
+SERS = plus4-stdser.ser
+
 TGIS =
 
 #--------------------------------------------------------------------------
@@ -80,14 +85,14 @@ TGIS =
 
 .PHONY:        all clean zap
 
-all:   $(OBJS) $(EMDS) $(JOYS) $(TGIS)
+all:   $(OBJS) $(EMDS) $(JOYS) $(SERS) $(TGIS)
 
 ../runtime/zeropage.o:
        $(MAKE) -C $(dir $@) $(notdir $@)
 
 clean:
-       @$(RM) $(OBJS) $(EMDS:.emd=.o) $(JOYS:.joy=.o) $(TGIS:.tgi=.o)
+       @$(RM) $(OBJS) $(EMDS:.emd=.o) $(JOYS:.joy=.o) $(SERS:.ser=.o) $(TGIS:.tgi=.o)
 
 zap:   clean
-       @$(RM) $(EMDS) $(JOYS) $(TGIS)
+       @$(RM) $(EMDS) $(JOYS) $(SERS) $(TGIS)                                 
 
index 52c77127c8f375d275335239eea3424c047926dd..37ead40bb50300f456c7aea110a1c96328eb302d 100644 (file)
@@ -55,11 +55,12 @@ uservec:            jmp     $FFFF           ; Patched at runtime
 
 
 
-; Break handler, called if a break occurs. Note: Y is not on the stack!
+; Break handler, called if a break occurs. 
 
 .proc  brk_handler
 
-               sty     _brk_y
+       pla
+               sta     _brk_y
        pla
        sta     _brk_x
        pla
index 3146e36742a6fab4720e3392a5e53babef0d1a8a..d6039a36b3986f942139eb0a6eb6cfd7b6060a48 100644 (file)
@@ -41,33 +41,19 @@ Head:   .word   @Next
                ldx     #zpspace-1
 L1:    lda     sp,x
        sta     zpsave,x        ; save the zero page locations we need
-       dex
+       dex
                bpl     L1
         sta     ENABLE_ROM
         cli
 
 ; Close open files
 
-       jsr     $FFCC           ; CLRCH
+       jsr     $FFCC           ; CLRCH
 
 ; Switch to second charset
 
-       lda     #14
-       jsr     $FFD2           ; BSOUT
-
-; Setup the IRQ vector in the banked RAM and switch off the ROM
-
-        sei                     ; No ints, handler not yet in place
-        sta     ENABLE_RAM
-        lda     #<IRQ
-        sta     $FFFE           ; Install interrupt handler
-        lda     #>IRQ
-        sta     $FFFF
-        cli                     ; Allow interrupts
-
-; Clear the BSS data
-
-       jsr     zerobss
+       lda     #14
+       jsr     $FFD2           ; BSOUT
 
 ; Save system stuff and setup the stack. The stack starts at the top of the
 ; usable RAM.
@@ -80,88 +66,110 @@ L1:        lda     sp,x
         lda     #>$FD00
         sta     sp+1
 
-; Call module constructors
+; Setup the IRQ vector in the banked RAM and switch off the ROM
 
-       jsr     initlib
+        ldx     #<IRQ
+        ldy     #>IRQ
+        sei                     ; No ints, handler not yet in place
+        sta     ENABLE_RAM
+        stx     $FFFE           ; Install interrupt handler
+        sty     $FFFF
+        cli                     ; Allow interrupts
 
-; If we have IRQ functions, chain our stub into the IRQ vector
+; Clear the BSS data
 
-        lda     #<__IRQFUNC_COUNT__
-       beq     NoIRQ1
-       lda     IRQVec
-               ldx     IRQVec+1
-       sta     IRQInd+1
-       stx     IRQInd+2
-       lda     #<IRQStub
-       ldx     #>IRQStub
-       sei
-       sta     IRQVec
-       stx     IRQVec+1
-       cli
+       jsr     zerobss
 
-; Push arguments and call main()
+; Call module constructors
+
+       jsr     initlib
 
-NoIRQ1: jsr            callmain
+; Initialize irqcount, which means that from now own custom linked in IRQ
+; handlers (via condes) will be called.
 
-; Back from main (this is also the _exit entry). Reset the IRQ vector if
-; we chained it.
+        lda     #.lobyte(__IRQFUNC_COUNT__*2)
+        sta     irqcount
 
-_exit:  lda     #<__IRQFUNC_COUNT__
-       beq     NoIRQ2
-       lda     IRQInd+1
-       ldx     IRQInd+2
-       sei
-       sta     IRQVec
-       stx     IRQVec+1
-       cli
+; Push arguments and call main()
+
+       jsr     callmain
 
-; Run module destructors.
+; Back from main (this is also the _exit entry). Run module destructors.
 
-NoIRQ2:        jsr     donelib         ; Run module destructors
+_exit:         lda     #0
+        sta     irqcount        ; Disable custom IRQ handlers
+        jsr    donelib         ; Run module destructors
 
 ; Restore system stuff
 
-       ldx     spsave
-       txs
+               ldx     spsave
+               txs
 
 ; Copy back the zero page stuff
 
-       ldx     #zpspace-1
+       ldx     #zpspace-1
 L2:    lda     zpsave,x
-       sta     sp,x
-       dex
+       sta     sp,x
+       dex
                bpl     L2
 
 ; Enable the ROM, reset changed vectors and return to BASIC
 
         sta     ENABLE_ROM
-       jmp     $FF8A           ; RESTOR
+       jmp     $FF8A           ; RESTOR
 
 
 ; ------------------------------------------------------------------------
-; IRQ handler
+; IRQ handler. The handler in the ROM enables the kernal and jumps to
+; $CE00, where the ROM code checks for a BRK or IRQ and branches via the
+; indirect vectors at $314/$316.
+; To make our stub as fast as possible, we skip the whole part of the ROM
+; handler and jump to the indirect vectors directly. We do also call our
+; own interrupt handlers if we have any, so they need not use $314.
 
 .segment        "LOWCODE"
 
-IRQ:    pha
+IRQ:    cld                    ; Just to be sure
+       pha
         txa
         pha
+       tya
+       pha
         tsx                     ; Get the stack pointer
-        lda     $0103,x         ; Get the saved status register
-        tax                     ; Save for later
+        lda     $0104,x         ; Get the saved status register
         and     #$10            ; Test for BRK bit
         bne     dobreak
-        lda     #>irq_ret       ; Push new return address
+
+; It's an IRQ and RAM is enabled. If we have handlers, call them. We will use
+; a flag here instead of loading __IRQFUNC_COUNT__ directly, since the condes
+; function is not reentrant. The irqcount flag will be set/reset from the main
+; code, to avoid races.
+
+       ldy     irqcount
+       beq     @L1
+               lda     #<__IRQFUNC_TABLE__
+       ldx     #>__IRQFUNC_TABLE__
+       jsr     condes                  ; Call the IRQ functions
+
+; Since the ROM handler will end with an RTI, we have to fake an IRQ return
+; on stack, so we get control of the CPU after the ROM handler and can switch
+; back to RAM.
+
+@L1:    lda     #>irq_ret       ; Push new return address
         pha
         lda     #<irq_ret
         pha
-        txa
-        pha
+               php                     ; Push faked IRQ frame on stack
+       pha                     ; Push faked A register
+       pha                     ; Push faked X register
+       pha                     ; Push faked Y register
         sta     ENABLE_ROM      ; Switch to ROM
-        jmp     ($FFFE)         ; Jump to kernal irq handler
+        jmp     (IRQVec)        ; Jump indirect to kernal irq handler
 
 irq_ret:
         sta     ENABLE_RAM      ; Switch back to RAM
+       pla
+       tay
         pla
         tax
         pla
@@ -175,27 +183,9 @@ dobreak:
 ; No break handler installed, jump to ROM
 
 nohandler:
-        tya
-        pha                     ; ROM handler expects Y on stack
         sta     ENABLE_ROM
         jmp     (BRKVec)        ; Jump indirect to the break vector
 
-; ------------------------------------------------------------------------
-; Stub for the IRQ chain. Is used only if there are IRQs defined. Needed in
-; low memory because of the banking.
-
-.segment        "LOWCODE"
-
-IRQStub:
-        cld                     ; Just to be sure
-        sta     ENABLE_RAM      ; Switch to RAM
-       ldy     #<(__IRQFUNC_COUNT__*2)
-               lda     #<__IRQFUNC_TABLE__
-       ldx     #>__IRQFUNC_TABLE__
-       jsr     condes                  ; Call the IRQ functions
-       sta     ENABLE_ROM
-               jmp     IRQInd                  ; Jump to the saved IRQ vector
-
 ; ------------------------------------------------------------------------
 ; Data
 
@@ -205,8 +195,8 @@ zpsave:             .res    zpspace
 ; BRK handling
 brk_jmp:        jmp     $0000
 
-.bss
 spsave:                .res    1
 
-
+.bss
+irqcount:       .byte   0
 
diff --git a/libsrc/plus4/plus4-stdser.s b/libsrc/plus4/plus4-stdser.s
new file mode 100644 (file)
index 0000000..a5aace9
--- /dev/null
@@ -0,0 +1,424 @@
+;
+; Serial driver for the builtin 6551 ACIA of the Plus/4.
+;
+; Ullrich von Bassewitz, 2003-12-13
+;
+; 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        "plus4.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
+;
+
+.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.
+
+
+INSTALL:
+UNINSTALL:
+
+; Deactivate DTR and disable 6551 interrupts
+
+       lda     #%00001010
+               sta     ACIA_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
+
+        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.
+
+        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                            ; Run into IRQ instead
+
+;----------------------------------------------------------------------------
+; IRQ: Called from the builtin runtime IRQ handler as a subroutine. All
+; registers are already save, no parameters are passed and no return code
+; is expected.
+;
+
+IRQ:    lda            ACIA_STATUS     ; Check ACIA status for receive interrupt
+       and     #$08
+               beq     @L9             ; Jump if no ACIA interrupt
+        lda    ACIA_DATA       ; 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
+               bcc     @L1             ; Assert flow control if buffer space low
+@L9:    rts
+
+; Assert flow control if buffer space too low
+
+@L1:   lda     RtsOff
+       sta     ACIA_CMD
+       sta     Stopped
+        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
+