;
; Startup code for cc65 (CBM 500 version)
;

   	.export	     	_exit, BRKVec
        .export         __STARTUP__ : absolute = 1      ; Mark as startup

     	.import	   	_clrscr, initlib, donelib, callirq_y
     	.import	     	push0, callmain
	.import	   	__CHARRAM_START__, __CHARRAM_SIZE__, __VIDRAM_START__
	.import	       	__BSS_RUN__, __BSS_SIZE__, __EXTZP_RUN__
	.import	       	__INTERRUPTOR_COUNT__
	.import		scnkey, UDTIM

     	.include     	"zeropage.inc"
        .include        "extzp.inc"
     	.include       	"cbm510.inc"


; ------------------------------------------------------------------------
; BASIC header and a small BASIC program. Since it is not possible to start
; programs in other banks using SYS, the BASIC program will write a small
; machine code program into memory at $100 and start that machine code
; program. The machine code program will then start the machine language
; code in bank 0, which will initialize the system by copying stuff from
; the system bank, and start the application.
;
; Here's the basic program that's in the following lines:
;
; 10 for i=0 to 4
; 20 read j
; 30 poke 256+i,j
; 40 next i
; 50 sys 256
; 60 data 120,169,0,133,0
;
; The machine program in the data lines is:
;
; sei
; lda 	  #$00
; sta     $00 	       	<-- Switch to bank 0 after this command
;
; Initialization is not only complex because of the jumping from one bank
; into another. but also because we want to save memory, and because of
; this, we will use the system memory ($00-$3FF) for initialization stuff
; that is overwritten later.
;

.segment        "BASICHDR"

        .byte  	$03,$00,$11,$00,$0a,$00,$81,$20,$49,$b2,$30,$20,$a4,$20,$34,$00
   	.byte	$19,$00,$14,$00,$87,$20,$4a,$00,$27,$00,$1e,$00,$97,$20,$32,$35
   	.byte	$36,$aa,$49,$2c,$4a,$00,$2f,$00,$28,$00,$82,$20,$49,$00,$39,$00
   	.byte	$32,$00,$9e,$20,$32,$35,$36,$00,$4f,$00,$3c,$00,$83,$20,$31,$32
   	.byte	$30,$2c,$31,$36,$39,$2c,$30,$2c,$31,$33,$33,$2c,$30,$00,$00,$00

;------------------------------------------------------------------------------
; A table that contains values that must be transfered from the system zero
; page into out zero page. Contains pairs of bytes, first one is the address
; in the system ZP, second one is our ZP address. The table goes into page 2,
; but is declared here, because it is needed earlier.

.SEGMENT        "PAGE2"

.proc   transfer_table

        .byte   $9F, DEVNUM
        .byte   $CA, CURS_Y
        .byte   $CB, CURS_X
        .byte   $EC, CHARCOLOR

.endproc


;------------------------------------------------------------------------------
; Page 3 data. This page contains the break vector and the bankswitch
; subroutine that is copied into high memory on startup. The space occupied by
; this routine will later be used for a copy of the bank 15 stack. It must be
; saved, since we're going to destroy it when calling bank 15.

.segment        "PAGE3"

BRKVec: .addr   _exit           ; BRK indirect vector

.proc   callbank15

        excrts  = $FEFE

.org    $FEC3

entry:  php
        pha
        lda     #$0F                    ; Bank 15
        sta     IndReg
        txa
        pha
        tya
        pha
        sei
        ldy     #$FF
        lda     (sysp1),y
        tay
        lda     ExecReg
        sta     (sysp1),y
        dey

        lda     #.hibyte(excrts-1)
        sta     (sysp1),y
        dey
        lda     #.lobyte(excrts-1)
        sta     (sysp1),y

        tya
        sec
        sbc     #7
        sta     $1FF                    ; Save new sp
        tay

        tsx

        pla
        iny
        sta     (sysp1),y
        pla
        iny
        sta     (sysp1),y
        pla
        iny
        sta     (sysp1),y
        pla
        iny
        sta     (sysp1),y

        lda     $105,x
        sec
        sbc     #3
        iny
        sta     (sysp1),y
        lda     $106,x
        sbc     #0
        iny
        sta     (sysp1),y

        ldy     $1FF                    ; Restore sp in bank 15

        lda     #.hibyte(expull-1)
        sta     (sysp1),y
        dey
        lda     #.lobyte(expull-1)
        sta     (sysp1),y
        dey
        pla
        pla
        tsx
        stx     $1FF
        tya
        tax
        txs
        lda     IndReg
        jmp     $FFF6

expull: pla
        tay
        pla
        tax
        pla
        plp
        rts

.if (expull <> $FF26)
.error "Symbol expull must be aligned with kernal in bank 15"
.endif

.reloc

.endproc

;------------------------------------------------------------------------------
; The code in the target bank when switching back will be put at the bottom
; of the stack. We will jump here to switch segments. The range $F2..$FF is
; not used by any kernal routine.

.segment        "STARTUP"

Back:   sta	ExecReg

; We are at $100 now. The following snippet is a copy of the code that is poked
; in the system bank memory by the basic header program, it's only for
; documentation and not actually used here:

     	sei
     	lda	#$00
     	sta	ExecReg

; This is the actual starting point of our code after switching banks for
; startup. Beware: The following code will get overwritten as soon as we
; use the stack (since it's in page 1)! We jump to another location, since
; we need some space for subroutines that aren't used later.

        jmp     Origin

; Hardware vectors, copied to $FFFA

.proc   vectors
        sta     ExecReg
        rts
        nop
       	.word	nmi             ; NMI vector
       	.word	0     	      	; Reset - not used
       	.word	irq             ; IRQ vector
.endproc

; Initializers for the extended zeropage. See extzp.s

.proc   extzp
        .word   $0100           ; sysp1
        .word   $0300           ; sysp3
        .word  	$d800           ; vic
	.word  	$da00           ; sid
	.word  	$db00           ; cia1
  	.word  	$dc00           ; cia2
  	.word  	$dd00           ; acia
  	.word  	$de00           ; tpi1
  	.word  	$df00           ; tpi2
  	.word  	$eab1           ; ktab1
  	.word  	$eb11           ; ktab2
  	.word	$eb71           ; ktab3
  	.word  	$ebd1           ; ktab4
.endproc

; Switch the indirect segment to the system bank

Origin: lda	#$0F
      	sta	IndReg

; Initialize the extended zeropage

        ldx     #.sizeof(extzp)-1
L1:     lda     extzp,x
        sta     <__EXTZP_RUN__,x
        dex
        bpl     L1

; Save the old stack pointer from the system bank and setup our hw sp

        tsx
        txa
        ldy     #$FF
        sta     (sysp1),y       ; Save system stack point into $F:$1FF
 	ldx	#$FE            ; Leave $1FF untouched for cross bank calls
 	txs	       	       	; Set up our own stack

; Copy stuff from the system zeropage to ours

        lda     #.sizeof(transfer_table)
        sta     ktmp
L2:     ldx     ktmp
        ldy     transfer_table-2,x
        lda     transfer_table-1,x
        tax
        lda     (sysp0),y
        sta     $00,x
        dec     ktmp
        dec     ktmp
        bne     L2

; Set the interrupt, NMI and other vectors

      	ldx    	#.sizeof(vectors)-1
L3:    	lda    	vectors,x
      	sta	$10000 - .sizeof(vectors),x
      	dex
       	bpl     L3

; Setup the C stack

	lda    	#.lobyte(callbank15::entry)
     	sta	sp
	lda   	#.hibyte(callbank15::entry)
	sta	sp+1

; Setup the subroutine and jump vector table that redirects kernal calls to
; the system bank.

        ldy     #.sizeof(callbank15)
@L1:    lda     callbank15-1,y
        sta     callbank15::entry-1,y
        dey
        bne     @L1

; Setup the jump vector table. Y is zero on entry.

        ldx     #45-1                   ; Number of vectors
@L2:    lda     #$20                    ; JSR opcode
        sta     $FF6F,y
        iny
        lda     #.lobyte(callbank15::entry)
        sta     $FF6F,y
        iny
        lda     #.hibyte(callbank15::entry)
        sta     $FF6F,y
        iny
        dex
        bpl     @L2

; Set the indirect segment to bank we're executing in

     	lda	ExecReg
 	sta	IndReg

; Zero the BSS segment. We will do that here instead calling the routine
; in the common library, since we have the memory anyway, and this way,
; it's reused later.

   	lda	#<__BSS_RUN__
   	sta	ptr1
	lda	#>__BSS_RUN__
   	sta	ptr1+1
	lda	#0
	tay

; Clear full pages

   	ldx	#>__BSS_SIZE__
   	beq	Z2
Z1:	sta	(ptr1),y
   	iny
   	bne	Z1
   	inc	ptr1+1			; Next page
     	dex
   	bne	Z1

; Clear the remaining page

Z2:	ldx	#<__BSS_SIZE__
   	beq	Z4
Z3:	sta	(ptr1),y
   	iny
   	dex
   	bne	Z3
Z4:     jmp     Init

; ------------------------------------------------------------------------
; We are at $200 now. We may now start calling subroutines safely, since
; the code we execute is no longer in the stack page.

.segment        "PAGE2"

; Copy the character rom from the system bank into the execution bank

Init:   lda     #<$C000
	sta	ptr1
	lda	#>$C000
	sta	ptr1+1
        lda	#<__CHARRAM_START__
	sta	ptr2
	lda	#>__CHARRAM_START__
	sta	ptr2+1
       	lda    	#>__CHARRAM_SIZE__     	; 16 * 256 bytes to copy
	sta	tmp1
	ldy	#$00
ccopy:	lda	#$0F
     	sta	IndReg	      		; Access the system bank
ccopy1:	lda	(ptr1),y
       	sta	__VIDRAM_START__,y
       	iny
       	bne	ccopy1
       	lda	ExecReg
       	sta	IndReg
ccopy2:	lda	__VIDRAM_START__,y
       	sta	(ptr2),y
       	iny
       	bne	ccopy2
       	inc	ptr1+1
       	inc	ptr2+1	      		; Bump high pointer bytes
       	dec	tmp1
       	bne	ccopy

; Clear the video memory. We will do this before switching the video to bank 0
; to avoid garbage when doing so.

        jsr     _clrscr

; Reprogram the VIC so that the text screen and the character ROM is in the
; execution bank. This is done in three steps:

        lda     #$0F  	      		; We need access to the system bank
       	sta	IndReg

; Place the VIC video RAM into bank 0
; CA (STATVID)   = 0
; CB (VICDOTSEL) = 0

       	ldy	#TPI::CR
       	lda	(tpi1),y
       	sta	vidsave+0
       	and	#%00001111
       	ora	#%10100000
       	sta	(tpi1),y

; Set bit 14/15 of the VIC address range to the high bits of __VIDRAM_START__
; PC6/PC7 (VICBANKSEL 0/1) = 11

       	ldy    	#TPI::PRC
       	lda	(tpi2),y
       	sta	vidsave+1
       	and	#$3F
       	ora    	#<((>__VIDRAM_START__) & $C0)
       	sta	(tpi2),y

; Set the VIC base address register to the addresses of the video and
; character RAM.

        ldy	#VIC_VIDEO_ADR
       	lda	(vic),y
       	sta	vidsave+2
	and	#$01
       	ora    	#<(((__VIDRAM_START__ >> 6) & $F0) | ((__CHARRAM_START__ >> 10) & $0E) | $02)
;      	and	#$0F
;      	ora    	#<(((>__VIDRAM_START__) << 2) & $F0)
       	sta	(vic),y

; Switch back to the execution bank

        lda     ExecReg
       	sta	IndReg

; Activate chained interrupt handlers, then enable interrupts.

        lda     #.lobyte(__INTERRUPTOR_COUNT__*2)
        sta     irqcount
      	cli

; Call module constructors.

        jsr	initlib

; Push arguments and call main()

       	jsr    	callmain

; Call module destructors. This is also the _exit entry and the default entry
; point for the break vector.

_exit:  pha			; Save the return code on stack
        jsr	donelib	     	; Run module destructors
	lda     #$00
        sta     irqcount        ; Disable custom irq handlers

; Address the system bank

        lda     #$0F
        sta     IndReg

; Switch back the video to the system bank

  	ldy	#TPI::CR
	lda	vidsave+0
  	sta	(tpi1),y

	ldy    	#TPI::PRC
   	lda	vidsave+1
   	sta	(tpi2),y

        ldy	#VIC_VIDEO_ADR
	lda	vidsave+2
	sta	(vic),y

; Copy stuff back from our zeropage to the systems

.if 0
        lda     #.sizeof(transfer_table)
        sta     ktmp
@L0:    ldx     ktmp
        ldy     transfer_table-2,x
        lda     transfer_table-1,x
        tax
        lda     $00,x
        sta     (sysp0),y
        dec     ktmp
        dec     ktmp
        bne     @L0
.endif

; Place the program return code into ST

	pla
	ldy	#$9C		; ST
	sta	(sysp0),y

; Setup the welcome code at the stack bottom in the system bank.

        ldy     #$FF
        lda     (sysp1),y       ; Load system bank sp
        tax
        iny                     ; Y = 0
        lda     #$58            ; CLI opcode
        sta     (sysp1),y
        iny
        lda     #$60            ; RTS opcode
        sta     (sysp1),y
   	lda    	IndReg
        sei
   	txs
        jmp     Back

; -------------------------------------------------------------------------
; The IRQ handler goes into PAGE2. For performance reasons, and to allow
; easier chaining, we do handle the IRQs in the execution bank (instead of
; passing them to the system bank).

; This is the mapping of the active irq register of the	6525 (tpi1):
;
; Bit   7       6       5       4       3       2       1       0
;                               |       |       |       |       ^ 50 Hz
;                               |       |       |       ^ SRQ IEEE 488
;                               |       |       ^ cia
;                               |       ^ IRQB ext. Port
;                               ^ acia

irq:    pha
        txa
        pha
        tya
        pha
        lda     IndReg
        pha
        lda     ExecReg
        sta     IndReg                  ; Be sure to address our segment
	tsx
       	lda    	$105,x	    		; Get the flags from the stack
	and	#$10	    		; Test break flag
       	bne     dobrk

; It's an IRQ

        cld

; Call chained IRQ handlers

       	ldy    	irqcount
        beq     irqskip
       	jsr    	callirq_y               ; Call the functions

; Done with chained IRQ handlers, check the TPI for IRQs and handle them

irqskip:lda	#$0F
	sta	IndReg
	ldy	#TPI::AIR
	lda	(tpi1),y		; Interrupt Register 6525
	beq	noirq

; 50/60Hz interrupt

	cmp	#%00000001		; ticker irq?
	bne	irqend
       	jsr     scnkey                  ; Poll the keyboard
        jsr	UDTIM                   ; Bump the time

; Done

irqend:	ldy	#TPI::AIR
       	sta	(tpi1),y    		; Clear interrupt

noirq:	pla
        sta     IndReg
        pla
        tay
        pla
        tax
        pla
nmi:	rti

dobrk:  jmp	(BRKVec)

; -------------------------------------------------------------------------
; Data area

.data
vidsave:.res	3

.bss
irqcount:       .byte   0


