;
; Driver for the AppleMouse II Card.
;
; Oliver Schmidt, 03.09.2005
;

        .include        "zeropage.inc"
        .include        "mouse-kernel.inc"
        .include        "apple2.inc"

; ------------------------------------------------------------------------

SETMOUSE        = $12   ; Sets mouse mode
SERVEMOUSE      = $13   ; Services mouse interrupt
READMOUSE       = $14   ; Reads mouse position
CLEARMOUSE      = $15   ; Clears mouse position to 0 (for delta mode)
POSMOUSE        = $16   ; Sets mouse position to a user-defined pos
CLAMPMOUSE      = $17   ; Sets mouse bounds in a window
HOMEMOUSE       = $18   ; Sets mouse to upper-left corner of clamp win
INITMOUSE       = $19   ; Resets mouse clamps to default values and
                        ; sets mouse position to 0,0

pos1_lo         := $0478
pos1_hi         := $0578
pos2_lo         := $04F8
pos2_hi         := $05F8
status          := $0778

; ------------------------------------------------------------------------

        .segment        "JUMPTABLE"

        ; Driver signature
        .byte   $6D, $6F, $75           ; "mou"
        .byte   MOUSE_API_VERSION       ; Mouse driver API version number

        ; Library reference
        .addr   $0000

        ; Jump table
        .addr   INSTALL
        .addr   UNINSTALL
        .addr   HIDE
        .addr   SHOW
        .addr   SETBOX
        .addr   GETBOX
        .addr   MOVE
        .addr   BUTTONS
        .addr   POS
        .addr   INFO
        .addr   IOCTL
        .addr   IRQ

        ; Mouse driver flags
        .byte   MOUSE_FLAG_EARLY_IRQ

        ; Callback table, set by the kernel before INSTALL is called
CHIDE:  jmp     $0000                   ; Hide the cursor
CSHOW:  jmp     $0000                   ; Show the cursor
CMOVEX: jmp     $0000                   ; Move the cursor to X coord
CMOVEY: jmp     $0000                   ; Move the cursor to Y coord

; ------------------------------------------------------------------------

        .bss

box:    .tag    MOUSE_BOX
info:   .tag    MOUSE_INFO
slot:   .res    1
visible:.res    1

; ------------------------------------------------------------------------

        .rodata

offsets:.byte   $05             ; Pascal 1.0 ID byte
        .byte   $07             ; Pascal 1.0 ID byte
        .byte   $0B             ; Pascal 1.1 generic signature byte
        .byte   $0C             ; Device signature byte

values: .byte   $38             ; Fixed
        .byte   $18             ; Fixed
        .byte   $01             ; Fixed
        .byte   $20             ; X-Y pointing device type 0

size    = * - values

inibox: .word     0             ; MinX
        .word     0             ; MinY
        .word   279             ; MaxX
        .word   191             ; MaxY

; ------------------------------------------------------------------------

        .data

firmware:
        ; Lookup and patch firmware address lobyte
lookup: ldy     $FF00,x         ; Patched at runtime
        sty     jump+1          ; Modify code below

        ; Apple II Mouse TechNote #1, Interrupt Environment with the Mouse:
        ; "Enter all mouse routines (...) with the X register set to $Cn
        ;  and Y register set to $n0, where n = the slot number."
xparam: ldx     #$FF            ; Patched at runtime
yparam: ldy     #$FF            ; Patched at runtime

jump:   jmp     $FFFF           ; Patched at runtime

; ------------------------------------------------------------------------

        .code

; INSTALL: Is called after the driver is loaded into memory. If possible,
; check if the hardware is present. Must return an MOUSE_ERR_xx code in A/X.
INSTALL:
        lda     #<$C000
        sta     ptr1
        lda     #>$C000
        sta     ptr1+1

        ; Search for AppleMouse II firmware in slots 1 - 7
next:   inc     ptr1+1
        lda     ptr1+1
        cmp     #>$C800
        bcc     :+

        ; Mouse firmware not found
        lda     #<MOUSE_ERR_NO_DEVICE
        ldx     #>MOUSE_ERR_NO_DEVICE
        rts

        ; Check Pascal 1.1 Firmware Protocol ID bytes
:       ldx     #size - 1
:       ldy     offsets,x
        lda     values,x
        cmp     (ptr1),y
        bne     next
        dex
        bpl     :-

        ; Get and patch firmware address hibyte
        lda     ptr1+1
        sta     lookup+2
        sta     xparam+1
        sta     jump+2

        ; Disable interrupts now because setting the slot number makes 
        ; the IRQ handler (maybe called due to some non-mouse IRQ) try
        ; calling the firmware which isn't correctly set up yet
        sei

        ; Convert to and save slot number
        and     #$0F
        sta     slot

        ; Convert to and patch I/O register index
        asl
        asl
        asl
        asl
        sta     yparam+1
        
        ; The AppleMouse II Card needs the ROM switched in
        ; to be able to detect an Apple //e and use RDVBL
        bit     $C082

        ; Reset mouse hardware
        ldx     #INITMOUSE
        jsr     firmware
        
        ; Switch in LC bank 2 for R/O
        bit     $C080

        ; Turn mouse on
        lda     #%00000001
        ldx     #SETMOUSE
        jsr     firmware

        ; Set initial mouse clamps
        lda     #<inibox
        ldx     #>inibox
        jsr     SETBOX

        ; Set initial mouse position
        ldx     slot
        lda     #<(279 / 2)
        sta     pos1_lo,x
        lda     #>(279 / 2)
        sta     pos1_hi,x
        lda     #<(191 / 2)
        sta     pos2_lo,x
        lda     #>(191 / 2)
        sta     pos2_hi,x
        ldx     #POSMOUSE
        jsr     firmware

        ; Update cursor
        jsr     update

        ; Turn VBL interrupt on
        lda     #%00001001
        ldx     #SETMOUSE
common: jsr     firmware

        ; Enable interrupts and return success
        cli
        lda     #<MOUSE_ERR_OK
        ldx     #>MOUSE_ERR_OK
        rts

; UNINSTALL: Is called before the driver is removed from memory.
; No return code required (the driver is removed from memory on return).
UNINSTALL:
        ; Hide cursor
        sei
        jsr     CHIDE

        ; Turn mouse off
        lda     #%00000000
        ldx     #SETMOUSE
        bne     common          ; Branch always

; SETBOX: Set the mouse bounding box. The parameters are passed as they come
; from the C program, that is, a pointer to a mouse_box struct in A/X.
; No checks are done if the mouse is currently inside the box, this is the job
; of the caller. It is not necessary to validate the parameters, trust the
; caller and save some code here. No return code required.
SETBOX:
        sta     ptr1
        stx     ptr1+1
        
        ; Set x clamps
        ldx     #$00
        ldy     #MOUSE_BOX::MINX
        jsr     :+
        
        ; Set y clamps
        ldx     #$01
        ldy     #MOUSE_BOX::MINY

        ; Apple II Mouse TechNote #1, Interrupt Environment with the Mouse:
        ; "Disable interrupts before placing position information in the
        ;  screen holes."
:       sei

        ; Set low clamp
        lda     (ptr1),y
        sta     box,y
        sta     pos1_lo
        iny
        lda     (ptr1),y
        sta     box,y   
        sta     pos1_hi

        ; Skip one word
        iny
        iny

        ; Set high clamp
        iny
        lda     (ptr1),y
        sta     box,y   
        sta     pos2_lo
        iny
        lda     (ptr1),y
        sta     box,y   
        sta     pos2_hi

        txa
        ldx     #CLAMPMOUSE
        bne     common          ; Branch always

; GETBOX: Return the mouse bounding box. The parameters are passed as they
; come from the C program, that is, a pointer to a mouse_box struct in A/X.
GETBOX:
        sta     ptr1
        stx     ptr1+1

        ldy     #.sizeof(MOUSE_BOX)-1
:       lda     box,y
        sta     (ptr1),y
        dey
        bpl     :-
        rts

; MOVE: Move the mouse to a new position. The position is passed as it comes
; from the C program, that is: x on the stack and y in A/X. The C wrapper will
; remove the parameter from the stack on return.
; No checks are done if the new position is valid (within the bounding box or
; the screen). No return code required.
MOVE:
        ldy     slot
        sei

        ; Set y
        sta     pos2_lo,y
        txa
        sta     pos2_hi,y

        tya
        tax
        ldy     #$00            ; Start at top of stack

        ; Set x
        lda     (sp),y
        iny
        sta     pos1_lo,x
        lda     (sp),y
        sta     pos1_hi,x

        ; Update cursor
        jsr     update

        ldx     #POSMOUSE
        bne     common          ; Branch always

; HIDE: Is called to hide the mouse cursor. The mouse kernel manages a
; counter for calls to show/hide, and the driver entry point is only called
; if the mouse is currently visible and should get hidden. For most drivers,
; no special action is required besides hiding the mouse cursor.
; No return code required.
HIDE:
        dec     visible
        sei
        jsr     CHIDE
        cli
        rts

; SHOW: Is called to show the mouse cursor. The mouse kernel manages a
; counter for calls to show/hide, and the driver entry point is only called
; if the mouse is currently hidden and should become visible. For most drivers,
; no special action is required besides enabling the mouse cursor.
; No return code required.
SHOW:
        inc     visible
        rts

; BUTTONS: Return the button mask in A/X.
BUTTONS:
        lda     info + MOUSE_INFO::BUTTONS
        ldx     #$00
        rts

; POS: Return the mouse position in the MOUSE_POS struct pointed to by ptr1.
; No return code required.
POS:
        ldy     #.sizeof(MOUSE_POS)-1
        bne     copy            ; Branch always

; INFO: Returns mouse position and current button mask in the MOUSE_INFO
; struct pointed to by ptr1. No return code required.
INFO:
        ldy     #.sizeof(MOUSE_INFO)-1
copy:   sei
:       lda     info,y
        sta     (ptr1),y
        dey
        bpl     :-
        cli
        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 MOUSE_ERR_xx code in A/X.
IOCTL:
        lda     #<MOUSE_ERR_INV_IOCTL
        ldx     #>MOUSE_ERR_INV_IOCTL
        rts

; IRQ: Called from the builtin runtime IRQ handler as a subroutine. All
; registers are already saved, 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:
        ; Check for installed mouse
        lda     slot
        beq     done

        ; Check for mouse interrupt
        ldx     #SERVEMOUSE
        jsr     firmware
        bcc     :+
        clc                     ; Interrupt not handled
done:   rts

:       ldx     #READMOUSE
        jsr     firmware

        ; Get status
        ldy     slot
        lda     status,y
        tax                     ; Save status

        ; Extract button down values
        asl                     ;  C = Button 0 is currently down
        and     #%00100000      ; !Z = Button 1 is currently down

        ; Set button mask
        beq     :+
        lda     #MOUSE_BTN_RIGHT
:       bcc     :+
        ora     #MOUSE_BTN_LEFT
:       sta     info + MOUSE_INFO::BUTTONS

        ; Check for mouse movement
        txa                     ; Restore status
        and     #%00100000      ; X or Y changed since last READMOUSE
        beq     :+

        ; Remove the cursor at the old position
update: jsr     CHIDE

        ; Get and set the new X position
        ldy     slot
        lda     pos1_lo,y
        ldx     pos1_hi,y
        sta     info + MOUSE_POS::XCOORD
        stx     info + MOUSE_POS::XCOORD+1
        jsr     CMOVEX

        ; Get and set the new Y position
        ldy     slot
        lda     pos2_lo,y
        ldx     pos2_hi,y
        sta     info + MOUSE_POS::YCOORD
        stx     info + MOUSE_POS::YCOORD+1
        jsr     CMOVEY

        ; Check for visibility
:       lda     visible
        beq     :+

        ; Draw the cursor at the new position
        jsr     CSHOW
:       sec                     ; Interrupt handled
        rts
