]> git.sur5r.net Git - cc65/blob - libsrc/c64/ser/c64-swlink.s
4751a24ae0120a57f500dedc6be66e8bbd73dc72
[cc65] / libsrc / c64 / ser / c64-swlink.s
1 ;
2 ; Serial driver for the C64 using a Swiftlink or Turbo-232 cartridge.
3 ;
4 ; Ullrich von Bassewitz, 2003-04-18
5 ;
6 ; The driver is based on the cc65 rs232 module, which in turn is based on
7 ; Craig Bruce device driver for the Switftlink/Turbo-232.
8 ;
9 ; SwiftLink/Turbo-232 v0.90 device driver, by Craig Bruce, 14-Apr-1998.
10 ;
11 ; This software is Public Domain.  It is in Buddy assembler format.
12 ;
13 ; This device driver uses the SwiftLink RS-232 Serial Cartridge, available from
14 ; Creative Micro Designs, Inc, and also supports the extensions of the Turbo232
15 ; Serial Cartridge.  Both devices are based on the 6551 ACIA chip.  It also
16 ; supports the "hacked" SwiftLink with a 1.8432 MHz crystal.
17 ;
18 ; The code assumes that the kernal + I/O are in context.  On the C128, call
19 ; it from Bank 15.  On the C64, don't flip out the Kernal unless a suitable
20 ; NMI catcher is put into the RAM under then Kernal.  For the SuperCPU, the
21 ; interrupt handling assumes that the 65816 is in 6502-emulation mode.
22 ;
23
24         .include        "zeropage.inc"
25         .include        "ser-kernel.inc"
26         .include        "ser-error.inc"
27         .include        "c64.inc"
28
29
30 ; ------------------------------------------------------------------------
31 ; Header. Includes jump table
32
33 .segment        "HEADER"
34
35 ; Driver signature
36
37         .byte   $73, $65, $72           ; "ser"
38         .byte   SER_API_VERSION         ; Serial API version number
39
40 ; Library reference
41
42         .addr   $0000
43
44 ; Jump table
45
46         .word   INSTALL
47         .word   UNINSTALL
48         .word   OPEN
49         .word   CLOSE
50         .word   GET
51         .word   PUT
52         .word   STATUS
53         .word   IOCTL
54         .word   IRQ
55
56 ;----------------------------------------------------------------------------
57 ; I/O definitions
58
59 ACIA            = $DE00
60 ACIA_DATA       = ACIA+0        ; Data register
61 ACIA_STATUS     = ACIA+1        ; Status register
62 ACIA_CMD        = ACIA+2        ; Command register
63 ACIA_CTRL       = ACIA+3        ; Control register
64
65 ;----------------------------------------------------------------------------
66 ;
67 ; Global variables
68 ;
69
70 ; We reuse the RS232 zero page variables for the driver, since the ROM
71 ; routines cannot be used together with this driver.
72 RecvHead        = $B5           ; Head of receive buffer
73 RecvTail        = $BD           ; Tail of receive buffer
74 RecvFreeCnt     = $F7           ; Number of bytes in receive buffer
75 SendHead        = $F8           ; Head of send buffer
76 SendTail        = $F9           ; Tail of send buffer
77 SendFreeCnt     = $FA           ; Number of bytes free in send buffer
78
79 .bss
80 Stopped:        .res    1       ; Flow-stopped flag
81 RtsOff:         .res    1       ;
82
83 ; Send and receive buffers: 256 bytes each
84 RecvBuf:        .res    256
85 SendBuf:        .res    256
86
87 .data
88 NmiContinue:    .byte   $4c     ; JMP instruction for NMI save -- continue
89 NmiSave:        .res    2       ; normal NMI handler
90
91 .rodata
92
93 ; Tables used to translate RS232 params into register values
94
95 BaudTable:                      ; bit7 = 1 means setting is invalid
96         .byte   $FF             ; SER_BAUD_45_5
97         .byte   $FF             ; SER_BAUD_50
98         .byte   $FF             ; SER_BAUD_75
99         .byte   $FF             ; SER_BAUD_110
100         .byte   $FF             ; SER_BAUD_134_5
101         .byte   $02             ; SER_BAUD_150
102         .byte   $05             ; SER_BAUD_300
103         .byte   $06             ; SER_BAUD_600
104         .byte   $07             ; SER_BAUD_1200
105         .byte   $FF             ; SER_BAUD_1800
106         .byte   $08             ; SER_BAUD_2400
107         .byte   $09             ; SER_BAUD_3600
108         .byte   $0A             ; SER_BAUD_4800
109         .byte   $0B             ; SER_BAUD_7200
110         .byte   $0C             ; SER_BAUD_9600
111         .byte   $0E             ; SER_BAUD_19200
112         .byte   $0F             ; SER_BAUD_38400
113         .byte   $FF             ; SER_BAUD_57600
114         .byte   $FF             ; SER_BAUD_115200
115         .byte   $FF             ; SER_BAUD_230400
116
117 BitTable:
118         .byte   $60             ; SER_BITS_5
119         .byte   $40             ; SER_BITS_6
120         .byte   $20             ; SER_BITS_7
121         .byte   $00             ; SER_BITS_8
122
123 StopTable:
124         .byte   $00             ; SER_STOP_1
125         .byte   $80             ; SER_STOP_2
126
127 ParityTable:
128         .byte   $00             ; SER_PAR_NONE
129         .byte   $20             ; SER_PAR_ODD
130         .byte   $60             ; SER_PAR_EVEN
131         .byte   $A0             ; SER_PAR_MARK
132         .byte   $E0             ; SER_PAR_SPACE
133
134 .code
135
136 ;----------------------------------------------------------------------------
137 ; INSTALL routine. Is called after the driver is loaded into memory. If
138 ; possible, check if the hardware is present.
139 ; Must return an SER_ERR_xx code in a/x.
140
141 INSTALL:
142
143 ; Deactivate DTR and disable 6551 interrupts
144
145         lda     #%00001010
146         sta     ACIA_CMD
147
148 ; Set up the nmi vector
149
150         lda     NMIVec
151         ldy     NMIVec+1
152         sta     NmiSave+0
153         sty     NmiSave+1
154         lda     #<NmiHandler
155         ldy     #>NmiHandler
156 SetNMI: sta     NMIVec
157         sty     NMIVec+1
158
159 ; Done, return an error code
160
161         lda     #<SER_ERR_OK
162         tax                     ; A is zero
163         rts
164
165 ;----------------------------------------------------------------------------
166 ; UNINSTALL routine. Is called before the driver is removed from memory.
167 ; Must return an SER_ERR_xx code in a/x.
168
169 UNINSTALL:
170
171 ; Stop interrupts, drop DTR
172
173         lda     #%00001010
174         sta     ACIA_CMD
175
176 ; Restore NMI vector and return OK
177
178         lda     NmiSave+0
179         ldy     NmiSave+1
180         jmp     SetNMI
181
182 ;----------------------------------------------------------------------------
183 ; PARAMS routine. A pointer to a ser_params structure is passed in ptr1.
184 ; Must return an SER_ERR_xx code in a/x.
185
186 OPEN:
187
188 ; Check if the handshake setting is valid
189
190         ldy     #SER_PARAMS::HANDSHAKE  ; Handshake
191         lda     (ptr1),y
192         cmp     #SER_HS_HW              ; This is all we support
193         bne     InvParam
194
195 ; Initialize buffers
196
197         jsr     InitBuffers
198
199 ; Set the value for the control register, which contains stop bits, word
200 ; length and the baud rate.
201
202         ldy     #SER_PARAMS::BAUDRATE
203         lda     (ptr1),y                ; Baudrate index
204         tay
205         lda     BaudTable,y             ; Get 6551 value
206         bmi     InvBaud                 ; Branch if rate not supported
207         sta     tmp1
208
209         ldy     #SER_PARAMS::DATABITS   ; Databits
210         lda     (ptr1),y
211         tay
212         lda     BitTable,y
213         ora     tmp1
214         sta     tmp1
215
216         ldy     #SER_PARAMS::STOPBITS   ; Stopbits
217         lda     (ptr1),y
218         tay
219         lda     StopTable,y
220         ora     tmp1
221         ora     #%00010000              ; Receiver clock source = baudrate
222         sta     ACIA_CTRL
223
224 ; Set the value for the command register. We remember the base value in
225 ; RtsOff, since we will have to manipulate ACIA_CMD often.
226
227         ldy     #SER_PARAMS::PARITY     ; Parity
228         lda     (ptr1),y
229         tay
230         lda     ParityTable,y
231         ora     #%00000001              ; DTR active
232         sta     RtsOff
233         ora     #%00001000              ; Enable receive interrupts
234         sta     ACIA_CMD
235
236 ; Done
237
238         lda     #<SER_ERR_OK
239         tax                             ; A is zero
240         rts
241
242 ; Invalid parameter
243
244 InvParam:
245         lda     #<SER_ERR_INIT_FAILED
246         ldx     #>SER_ERR_INIT_FAILED
247         rts
248
249 ; Baud rate not available
250
251 InvBaud:
252         lda     #<SER_ERR_BAUD_UNAVAIL
253         ldx     #>SER_ERR_BAUD_UNAVAIL
254         rts
255
256 ;----------------------------------------------------------------------------
257 ; CLOSE: Close the port, disable interrupts and flush the buffer. Called
258 ; without parameters. Must return an error code in a/x.
259 ;
260
261 CLOSE:
262
263 ; Stop interrupts, drop DTR
264
265         lda     #%00001010
266         sta     ACIA_CMD
267
268 ; Initalize buffers. Returns zero in a
269
270         jsr     InitBuffers
271
272 ; Return OK
273
274         lda     #<SER_ERR_OK
275         tax                             ; A is zero
276         rts
277
278 ;----------------------------------------------------------------------------
279 ; GET: Will fetch a character from the receive buffer and store it into the
280 ; variable pointer to by ptr1. If no data is available, SER_ERR_NO_DATA is
281 ; return.
282 ;
283
284 GET:    ldx     SendFreeCnt             ; Send data if necessary
285         inx                             ; X == $FF?
286         beq     @L1
287         lda     #$00
288         jsr     TryToSend
289
290 ; Check for buffer empty
291
292 @L1:    lda     RecvFreeCnt             ; (25)
293         cmp     #$ff
294         bne     @L2
295         lda     #<SER_ERR_NO_DATA
296         ldx     #>SER_ERR_NO_DATA
297         rts
298
299 ; Check for flow stopped & enough free: release flow control
300
301 @L2:    ldx     Stopped                 ; (34)
302         beq     @L3
303         cmp     #63
304         bcc     @L3
305         lda     #$00
306         sta     Stopped
307         lda     RtsOff
308         ora     #%00001000
309         sta     ACIA_CMD
310
311 ; Get byte from buffer
312
313 @L3:    ldx     RecvHead                ; (41)
314         lda     RecvBuf,x
315         inc     RecvHead
316         inc     RecvFreeCnt
317         ldx     #$00                    ; (59)
318         sta     (ptr1,x)
319         txa                             ; Return code = 0
320         rts
321
322 ;----------------------------------------------------------------------------
323 ; PUT: Output character in A.
324 ; Must return an error code in a/x.
325 ;
326
327 PUT:
328
329 ; Try to send
330
331         ldx     SendFreeCnt
332         inx                             ; X = $ff?
333         beq     @L2
334         pha
335         lda     #$00
336         jsr     TryToSend
337         pla
338
339 ; Put byte into send buffer & send
340
341 @L2:    ldx     SendFreeCnt
342         bne     @L3
343         lda     #<SER_ERR_OVERFLOW      ; X is already zero
344         rts
345
346 @L3:    ldx     SendTail
347         sta     SendBuf,x
348         inc     SendTail
349         dec     SendFreeCnt
350         lda     #$ff
351         jsr     TryToSend
352         lda     #<SER_ERR_OK
353         tax
354         rts
355
356 ;----------------------------------------------------------------------------
357 ; STATUS: Return the status in the variable pointed to by ptr1.
358 ; Must return an error code in a/x.
359 ;
360
361 STATUS: lda     ACIA_STATUS
362         ldx     #0
363         sta     (ptr1,x)
364         txa                             ; SER_ERR_OK
365         rts
366
367 ;----------------------------------------------------------------------------
368 ; IOCTL: Driver defined entry point. The wrapper will pass a pointer to ioctl
369 ; specific data in ptr1, and the ioctl code in A.
370 ; Must return an error code in a/x.
371 ;
372
373 IOCTL:  lda     #<SER_ERR_INV_IOCTL     ; We don't support ioclts for now
374         ldx     #>SER_ERR_INV_IOCTL
375        rts
376
377 ;----------------------------------------------------------------------------
378 ; IRQ: Not used on the C64
379 ;
380
381 IRQ     = $0000
382
383 ;----------------------------------------------------------------------------
384 ;
385 ; NMI handler
386 ; C128 NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=33, ROMexit=30
387 ; C64  NMI overhead=76 cycles: int=7, maxLatency=6, ROMenter=34, ROMexit=29
388 ;
389 ; timing: normal=76+43+9=128 cycles, assertFlow=76+52+9=137 cycles
390 ;
391 ; C128 @ 115.2k: 177 cycles avail (fast)
392 ; C64  @  57.6k: 177 cycles avail, worstAvail=177-43? = 134
393 ; SCPU @ 230.4k: 868 cycles avail: for a joke!
394 ;
395
396 NmiHandler:
397         pha
398         lda     ACIA_STATUS     ;(4) ;status ;check for byte received
399         and     #$08            ;(2)
400         beq     @L9             ;(2*)
401         cld
402         txa
403         pha
404         tya
405         pha
406 @L1:    lda     ACIA_DATA       ;(4)  data  ;get byte and put into receive buffer
407         ldy     RecvTail        ;(4)
408         ldx     RecvFreeCnt     ;(4)
409         beq     @L3             ;(2*) Jump if no space in receive buffer
410         sta     RecvBuf,y       ;(5)
411         inc     RecvTail        ;(6)
412         dec     RecvFreeCnt     ;(6)
413         cpx     #33             ;(2)  check for buffer space low
414         bcc     @L2             ;(2*)
415         jmp     NMIEXIT         ;(3)
416
417 ; Assert flow control
418
419 @L2:    lda     RtsOff          ;(3) assert flow control if buffer space too low
420         sta     ACIA_CMD        ;(4) command
421         sta     Stopped         ;(3)
422 @L3:    jmp     NMIEXIT         ;(3)
423
424 @L9:    pla
425         jmp     NmiContinue
426
427 ;----------------------------------------------------------------------------
428 ; Try to send a byte. Internal routine. A = TryHard
429
430 .proc   TryToSend
431
432         sta     tmp1            ; Remember tryHard flag
433 @L0:    lda     SendFreeCnt
434         cmp     #$ff
435         beq     @L3             ; Bail out
436
437 ; Check for flow stopped
438
439 @L1:    lda     Stopped
440         bne     @L3             ; Bail out
441
442 ; Check that swiftlink is ready to send
443
444 @L2:    lda     ACIA_STATUS
445         and     #$10
446         bne     @L4
447         bit     tmp1            ;keep trying if must try hard
448         bmi     @L0
449 @L3:    rts
450
451 ; Send byte and try again
452
453 @L4:    ldx     SendHead
454         lda     SendBuf,x
455         sta     ACIA_DATA
456         inc     SendHead
457         inc     SendFreeCnt
458         jmp     @L0
459
460 .endproc
461
462
463 ;----------------------------------------------------------------------------
464 ; Initialize buffers
465
466 InitBuffers:
467         ldx     #0
468         stx     Stopped
469         stx     RecvHead
470         stx     RecvTail
471         stx     SendHead
472         stx     SendTail
473         dex                             ; X = 255
474         stx     RecvFreeCnt
475         stx     SendFreeCnt
476         rts
477