]> git.sur5r.net Git - cc65/blob - libsrc/apple2/apple2-ssc.s
a0b35c5ad811a3a792fadab94cb12c95399610f4
[cc65] / libsrc / apple2 / apple2-ssc.s
1 ;
2 ; Serial driver for the Apple II Super Serial Card.
3 ;
4 ; Oliver Schmidt, 21.04.2005
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
28
29 ; ------------------------------------------------------------------------
30 ; Header. Includes jump table
31
32         .segment        "JUMPTABLE"
33
34         ; Driver signature
35         .byte   $73, $65, $72           ; "ser"
36         .byte   SER_API_VERSION         ; Serial API version number
37
38         ; Jump table.
39         .addr   INSTALL
40         .addr   UNINSTALL
41         .addr   OPEN
42         .addr   CLOSE
43         .addr   GET
44         .addr   PUT
45         .addr   STATUS
46         .addr   IOCTL
47         .addr   IRQ
48
49 ;----------------------------------------------------------------------------
50 ; I/O definitions
51
52 ACIA            = $C088
53 ACIA_DATA       = ACIA+0        ; Data register
54 ACIA_STATUS     = ACIA+1        ; Status register
55 ACIA_CMD        = ACIA+2        ; Command register
56 ACIA_CTRL       = ACIA+3        ; Control register
57
58 ;----------------------------------------------------------------------------
59 ; Global variables
60
61         .bss
62
63 RecvHead:       .res    1       ; Head of receive buffer
64 RecvTail:       .res    1       ; Tail of receive buffer
65 RecvFreeCnt:    .res    1       ; Number of bytes in receive buffer
66 SendHead:       .res    1       ; Head of send buffer
67 SendTail:       .res    1       ; Tail of send buffer
68 SendFreeCnt:    .res    1       ; Number of bytes in send buffer
69
70 Stopped:        .res    1       ; Flow-stopped flag
71 RtsOff:         .res    1       ;
72
73 RecvBuf:        .res    256     ; Receive buffers: 256 bytes
74 SendBuf:        .res    256     ; Send buffers: 256 bytes
75
76 Index:          .res    1       ; I/O register index
77
78         .data
79
80 Slot:   .byte   $02             ; Default to SSC in slot 2
81
82         .rodata
83
84         ; Tables used to translate RS232 params into register values
85 BaudTable:                      ; bit7 = 1 means setting is invalid
86         .byte   $FF             ; SER_BAUD_45_5
87         .byte   $01             ; SER_BAUD_50
88         .byte   $02             ; SER_BAUD_75
89         .byte   $03             ; SER_BAUD_110
90         .byte   $04             ; SER_BAUD_134_5
91         .byte   $05             ; SER_BAUD_150
92         .byte   $06             ; SER_BAUD_300
93         .byte   $07             ; SER_BAUD_600
94         .byte   $08             ; SER_BAUD_1200
95         .byte   $09             ; SER_BAUD_1800
96         .byte   $0A             ; SER_BAUD_2400
97         .byte   $0B             ; SER_BAUD_3600
98         .byte   $0C             ; SER_BAUD_4800
99         .byte   $0D             ; SER_BAUD_7200
100         .byte   $0E             ; SER_BAUD_9600
101         .byte   $0F             ; SER_BAUD_19200
102         .byte   $FF             ; SER_BAUD_38400
103         .byte   $FF             ; SER_BAUD_57600
104         .byte   $FF             ; SER_BAUD_115200
105         .byte   $FF             ; SER_BAUD_230400
106 BitTable:
107         .byte   $60             ; SER_BITS_5
108         .byte   $40             ; SER_BITS_6
109         .byte   $20             ; SER_BITS_7
110         .byte   $00             ; SER_BITS_8
111 StopTable:
112         .byte   $00             ; SER_STOP_1
113         .byte   $80             ; SER_STOP_2
114 ParityTable:
115         .byte   $00             ; SER_PAR_NONE
116         .byte   $20             ; SER_PAR_ODD
117         .byte   $60             ; SER_PAR_EVEN
118         .byte   $A0             ; SER_PAR_MARK
119         .byte   $E0             ; SER_PAR_SPACE
120 IdOfsTable:
121         .byte   $05             ; Pascal 1.0 ID byte
122         .byte   $07             ; Pascal 1.0 ID byte
123         .byte   $0B             ; Pascal 1.1 generic signature byte
124         .byte   $0C             ; Device signature byte
125 IdValTable:
126         .byte   $38             ; Fixed
127         .byte   $18             ; Fixed
128         .byte   $01             ; Fixed
129         .byte   $31             ; Serial or parallel I/O card type 1
130
131 IdTableLen      = * - IdValTable
132
133         .code
134
135 ;----------------------------------------------------------------------------
136 ; INSTALL: Is called after the driver is loaded into memory. If possible,
137 ; check if the hardware is present. Must return an SER_ERR_xx code in a/x.
138 ;
139 ; Since we don't have to manage the IRQ vector on the Apple II, this is
140 ; actually the same as:
141 ;
142 ; UNINSTALL: Is called before the driver is removed from memory.
143 ; No return code required (the driver is removed from memory on return).
144 ;
145 ; and:
146 ;
147 ; CLOSE: Close the port and disable interrupts. Called without parameters.
148 ; Must return an SER_ERR_xx code in a/x.
149
150 INSTALL:
151 UNINSTALL:
152 CLOSE:
153         ldx     Index           ; Check for open port
154         beq     :+
155
156         ; Deactivate DTR and disable 6551 interrupts
157         lda     #%00001010
158         sta     ACIA_CMD,x
159
160         ; Done, return an error code
161 :       lda     #<SER_ERR_OK
162         tax                     ; A is zero
163         stx     Index           ; Mark port as closed
164         rts
165
166 ;----------------------------------------------------------------------------
167 ; OPEN: A pointer to a ser_params structure is passed in ptr1.
168 ; Must return an SER_ERR_xx code in a/x.
169
170 OPEN:   
171         ldx     #<$C000
172         stx     ptr2
173         lda     #>$C000
174         ora     Slot
175         sta     ptr2+1
176         
177         ; Check Pascal 1.1 Firmware Protocol ID bytes
178 :       ldy     IdOfsTable,x
179         lda     IdValTable,x
180         cmp     (ptr2),y
181         bne     NoDevice
182         inx
183         cpx     #IdTableLen
184         bcc     :-
185         
186         ; Convert slot to I/O register index
187         lda     Slot
188         asl
189         asl
190         asl
191         asl
192         tax
193
194         ; Check if the handshake setting is valid
195         ldy     #SER_PARAMS::HANDSHAKE  ; Handshake
196         lda     (ptr1),y
197         cmp     #SER_HS_HW              ; This is all we support
198         bne     InvParam
199
200         ; Initialize buffers
201         ldy     #$00
202         sty     Stopped
203         sty     RecvHead
204         sty     RecvTail
205         sty     SendHead
206         sty     SendTail
207         dey                             ; Y = 255
208         sty     RecvFreeCnt
209         sty     SendFreeCnt
210
211         ; Set the value for the control register, which contains stop bits,
212         ; word length and the baud rate.
213         ldy     #SER_PARAMS::BAUDRATE
214         lda     (ptr1),y                ; Baudrate index
215         tay
216         lda     BaudTable,y             ; Get 6551 value
217         bmi     InvBaud                 ; Branch if rate not supported
218         sta     tmp1
219
220         ldy     #SER_PARAMS::DATABITS   ; Databits
221         lda     (ptr1),y
222         tay
223         lda     BitTable,y
224         ora     tmp1
225         sta     tmp1
226
227         ldy     #SER_PARAMS::STOPBITS   ; Stopbits
228         lda     (ptr1),y
229         tay
230         lda     StopTable,y
231         ora     tmp1
232         ora     #%00010000              ; Receiver clock source = baudrate
233         sta     ACIA_CTRL,x
234
235         ; Set the value for the command register. We remember the base value
236         ; in RtsOff, since we will have to manipulate ACIA_CMD often.
237         ldy     #SER_PARAMS::PARITY     ; Parity
238         lda     (ptr1),y
239         tay
240         lda     ParityTable,y
241         ora     #%00000001              ; DTR active
242         sta     RtsOff
243         ora     #%00001000              ; Enable receive interrupts
244         sta     ACIA_CMD,x
245
246         ; Done
247         stx     Index                   ; Mark port as open
248         lda     #<SER_ERR_OK
249         tax                             ; A is zero
250         rts
251
252         ; Device (hardware) not found
253 NoDevice:lda    #<SER_ERR_NO_DEVICE
254         ldx     #>SER_ERR_NO_DEVICE
255         rts
256
257         ; Invalid parameter
258 InvParam:lda    #<SER_ERR_INIT_FAILED
259         ldx     #>SER_ERR_INIT_FAILED
260         rts
261
262         ; Baud rate not available
263 InvBaud:lda     #<SER_ERR_BAUD_UNAVAIL
264         ldx     #>SER_ERR_BAUD_UNAVAIL
265         rts
266
267 ;----------------------------------------------------------------------------
268 ; GET: Will fetch a character from the receive buffer and store it into the
269 ; variable pointed to by ptr1. If no data is available, SER_ERR_NO_DATA is
270 ; returned.
271
272 GET:
273         ldx     Index
274         ldy     SendFreeCnt     ; Send data if necessary
275         iny                     ; Y == $FF?
276         beq     :+
277         lda     #$00            ; TryHard = false
278         jsr     TryToSend
279
280         ; Check for buffer empty
281 :       lda     RecvFreeCnt     ; (25)
282         cmp     #$FF
283         bne     :+
284         lda     #<SER_ERR_NO_DATA
285         ldx     #>SER_ERR_NO_DATA
286         rts
287
288         ; Check for flow stopped & enough free: release flow control
289 :       ldy     Stopped         ; (34)
290         beq     :+
291         cmp     #63
292         bcc     :+
293         lda     #$00
294         sta     Stopped
295         lda     RtsOff
296         ora     #%00001000
297         sta     ACIA_CMD,x
298
299         ; Get byte from buffer
300 :       ldy     RecvHead        ; (41)
301         lda     RecvBuf,y
302         inc     RecvHead
303         inc     RecvFreeCnt
304         ldx     #$00            ; (59)
305         sta     (ptr1,x)
306         txa                     ; Return code = 0
307         rts
308
309 ;----------------------------------------------------------------------------
310 ; PUT: Output character in A.
311 ; Must return an SER_ERR_xx code in a/x.
312
313 PUT:
314         ldx     Index
315
316         ; Try to send
317         ldy     SendFreeCnt
318         iny                     ; Y = $FF?
319         beq     :+
320         pha
321         lda     #$00            ; TryHard = false
322         jsr     TryToSend
323         pla
324
325         ; Put byte into send buffer & send
326 :       ldy     SendFreeCnt
327         bne     :+
328         lda     #<SER_ERR_OVERFLOW
329         ldx     #>SER_ERR_OVERFLOW
330         rts
331
332 :       ldy     SendTail
333         sta     SendBuf,y
334         inc     SendTail
335         dec     SendFreeCnt
336         lda     #$FF            ; TryHard = true
337         jsr     TryToSend
338         lda     #<SER_ERR_OK
339         tax
340         rts
341
342 ;----------------------------------------------------------------------------
343 ; STATUS: Return the status in the variable pointed to by ptr1.
344 ; Must return an SER_ERR_xx code in a/x.
345
346 STATUS:
347         ldx     Index
348         lda     ACIA_STATUS,x
349         ldx     #$00
350         sta     (ptr1,x)
351         txa                     ; SER_ERR_OK
352         rts
353
354 ;----------------------------------------------------------------------------
355 ; IOCTL: Driver defined entry point. The wrapper will pass a pointer to ioctl
356 ; specific data in ptr1, and the ioctl code in A.
357 ; Must return an SER_ERR_xx code in a/x.
358
359 IOCTL:
360         ; Check code to be 0
361         tax
362         bne     :+
363
364         ; Check data to be [1..7]
365         lda     (ptr1,x)
366         beq     :+
367         cmp     #7+1
368         bcs     :+
369
370         sta     Slot
371         txa                     ; SER_ERR_OK
372         rts
373
374 :       lda     #<SER_ERR_INV_IOCTL
375         ldx     #>SER_ERR_INV_IOCTL
376         rts
377
378 ;----------------------------------------------------------------------------
379 ; IRQ: Called from the builtin runtime IRQ handler as a subroutine. All
380 ; registers are already saved, no parameters are passed, but the carry flag
381 ; is clear on entry. The routine must return with carry set if the interrupt
382 ; was handled, otherwise with carry clear.
383
384 IRQ:
385         ldx     Index           ; Check for open port
386         beq     Done
387         lda     ACIA_STATUS,x   ; Check ACIA status for receive interrupt
388         and     #$08
389         beq     Done            ; Jump if no ACIA interrupt
390         lda     ACIA_DATA,x     ; Get byte from ACIA
391         ldy     RecvFreeCnt     ; Check if we have free space left
392         beq     Flow            ; Jump if no space in receive buffer
393         ldy     RecvTail        ; Load buffer pointer
394         sta     RecvBuf,y       ; Store received byte in buffer
395         inc     RecvTail        ; Increment buffer pointer
396         dec     RecvFreeCnt     ; Decrement free space counter
397         ldy     RecvFreeCnt     ; Check for buffer space low
398         cpy     #33
399         bcc     Flow            ; Assert flow control if buffer space low
400         rts                     ; Interrupt handled (carry already set)
401
402         ; Assert flow control if buffer space too low
403 Flow:   lda     RtsOff
404         sta     ACIA_CMD,x
405         sta     Stopped
406         sec                     ; Interrupt handled
407 Done:   rts
408
409 ;----------------------------------------------------------------------------
410 ; Try to send a byte. Internal routine. A = TryHard
411
412 TryToSend:
413         sta     tmp1            ; Remember tryHard flag
414 Again:  lda     SendFreeCnt
415         cmp     #$FF
416         beq     Quit            ; Bail out
417
418         ; Check for flow stopped
419         lda     Stopped
420         bne     Quit            ; Bail out
421
422         ; Check that ACIA is ready to send
423         lda     ACIA_STATUS,x
424         and     #$10
425         bne     Send
426         bit     tmp1            ; Keep trying if must try hard  
427         bmi     Again
428 Quit:   rts
429
430         ; Send byte and try again
431 Send:   ldy     SendHead
432         lda     SendBuf,y
433         sta     ACIA_DATA,x
434         inc     SendHead
435         inc     SendFreeCnt
436         jmp     Again