**************************************************************************** **************************************************************************** ** ** Platinum (Grayscale) ** ** This software is in the public domain. There is no warranty. ** ** by Patrick Davidson (pad@calc.org, http://pad.calc.org/) ** ** Last updated September 16, 2001 ** **************************************************************************** **************************************************************************** **************************************** Grayscale initialization * * This routine initializes grayscale with true synchronization to the * vertical refresh of the LCD itself, compatible with both HW1 and HW2. * It saves the addresses of plane0 and plane1 in the appropriate variables. * The light plane is plane0 and the dark plane is plane1. * * This routine assumes that all interrupt vectors have been unprotected. * ******** Activate_Grayscale: move.w #$400,d0 trap #1 lea old_int_5(pc),a0 move.l $74,(a0) move.l $C8,d1 ; Hardware check is based on example and.l #$600000,d1 ; code from Zeljko Juric move.l d1,a1 move.l 260(a1),a0 move.l a0,d2 sub.l d1,d2 cmp.l #$FFFF,d2 bhi hw1 cmpi.w #$16,(a0) bls hw1 move.l $16(a0),d1 subq.w #1,d1 beq hw1 trap #12 ; VTI check move.w #$3000,sr move.w sr,d1 move.w d0,sr btst #12,d1 bne hw1 **************************************** Prepare HW2 grayscale * * This routine must allocate two screen buffers, since the grayscale * interrupt has to copy from either one to the screen. * * After allocating memory, this routine times the display refresh rate by * waiting for one vertical blank, and then another, and counting the number * of increments of the programmable timer occur during this period. The * routine sets the increment rate to the highest possible value (around * 16 kHz) for the most precise timing; it should take about 184 increments. * The routine actually sets the timer to trigger 7 increments before to * make sure that it can copy the first two. lines before the frame sync. * The extra delay may waste several percent of the CPU time, but since HW2 * is much faster an HW2 calc can still do better than the maximum HW1 speed. * * Note that this routine decreases the display height on a HW2 calc to 117 * lines to match (approximately) the HW1 refresh rate. * ******** hw2: pea 3840*2 ; allocate two bitplanes JSR_ROM HeapAllocHigh addq.l #4,sp lea handle(pc),a4 ; load grayscale variable pointer move.w d0,(a4)+ ; save handle beq gs_fail move.b #256-117,$600013 move.w d0,-(sp) JSR_ROM HeapDeref ; get pointer to memory in A0 addq.l #2,sp move.l a0,(a4)+ ; store plane0 lea 3840(a0),a0 move.l a0,(a4)+ ; store plane1 bsr.s vwait_hw2 ; wait for one vertical refresh bclr #4,$600015 bclr #5,$600015 move.b #1,$600017 ; restart counter bsr.s vwait_hw2 ; wait for next vertical refresh move.b $600017,d0 ; D0 = # of frames taken neg.b d0 ; D0 = 256 - number of frames addq.b #8,d0 ; D0 = 257 - (number of frames - 7) move.b d0,$600017 ext.w d0 move.l d0,(a4)+ ; store interrupt start value clr.l (a4)+ ; clear page_count, vbl_count move.l #hw2_interrupt,$74 JSR_ROM OSContrastDn moveq #2,d0 rts vwait_hw2: lea $70001d,a0 move.b (a0),d0 \vwl: move.b (a0),d1 eor.b d0,d1 bpl.s \vwl rts gs_fail: moveq #0,d0 rts **************************************** HW1 grayscale setup * * This routine needs only allocate one screen buffer, since the regular LCD * buffer can be used under HW1. * * After the memory is allocated, the grayscale interrupt is set up and * the routine then waits for it to acknowledge one VBL (which will occur * right after one time that int1 is triggered, then pauses a few cycles * and re-initializes the display size, also resetting the display counters. * Since the LCD hardware's vertical sync at 1/4 the frequency of int1 * occurences, this results in syncrhonizing the LCD vertical sync to occur * slightly after every 4th subsequent interrupt 1. * * This slight delay ensures that the interrupt will be invoked with enough * time to set the new display buffer, and that after the interrupt returns * a routine which is waiting for the vbl flag to be set will be started a * few clock cycles before the next frame begins showing (with the newly * selected light buffer to be displayed). * ******** hw1: move.b #$f1,$600017 pea 3840+6 ; +6 makes 8-byte alignment possible JSR_ROM HeapAllocHigh addq.l #4,sp lea handle(pc),a4 ; load grayscale variable pointer move.w d0,(a4)+ ; save handle beq.s gs_fail move.w d0,-(sp) JSR_ROM HeapDeref ; get pointer to memory in A0 addq.l #2,sp move.l a0,d0 addq.l #6,d0 and.l #$FFFFFFF8,d0 ; shift address up to 8-byte boundary move.l d0,(a4)+ ; save plane 0 move.l #$4c00,(a4)+ ; save plane 1 lsr.l #3,d0 move.w d0,(a4)+ ; save gs_div8 move.w #$f1,(a4)+ ; save interrupt rate clr.l (a4)+ ; clear page_count, vbl_count lea hw1_interrupt(pc),a0 ; install interrupt move.l a0,$74 bsr Wait_VBL moveq #15,d0 \w2: dbra d0,\w2 ; Wait ~210 cycles after interrupt move.w #$3180,$600012 ; reset display counters moveq #1,d0 rts **************************************** HW1 interrupt dc.w 0 hw1_interrupt: addq.w #1,page_count ; increase page count cmp.w #3,page_count ; 3 = restart beq.s hw1_restart move.w #$980,$600010 ; page 1, 2 = show $4c00 hw1_exit: rte hw1_restart: move.w gs_div8(pc),$600010 ; page 0 = show allocated frame move.w #0,page_count addq.w #1,vbl_count ; mark next frame start rte **************************************** HW2 interrupt * * When page 0 reached, signals VBL (dark frame remains shown). * When page 2 reached, copies light frame. * When page 4 reached, copies dark frame. * ******* hw2_interrupt: movem.l d0-a6,-(sp) lea page_count(pc),a1 addq.w #2,(a1) ; go to next page move.w (a1),d3 cmp.w #6,d3 ; restart if page 6 beq \skip add.w d3,d3 move.l -16(a1,d3.w),a0 lea LCD_MEM,a1 movem.l (a0)+,d1-d7/a2-a6 ; copy first 150 bytes (5 lines) movem.l d1-d7/a2-a6,(a1) movem.l (a0)+,d1-d7/a2-a6 movem.l d1-d7/a2-a6,48(a1) movem.l (a0)+,d0-d7/a2-a6 movem.l d0-d7/a2-a6,96(a1) lea 148(a1),a1 move.w (a0)+,(a1)+ lea $70001d,a2 ; wait for vertical blank move.b (a2),d2 \vwl: move.b (a2),d1 eor.b d2,d1 bpl.s \vwl move.w int_start(pc),$600016 ; restart timer for next frame moveq #13,d0 ; copy last 3510 bytes \copy: movem.l (a0)+,d1-d7/a2-a6 movem.l d1-d7/a2-a6,(a1) movem.l (a0)+,d1-d7/a2-a6 movem.l d1-d7/a2-a6,48(a1) movem.l (a0)+,d1-d7/a2-a6 movem.l d1-d7/a2-a6,96(a1) movem.l (a0)+,d1-d7/a2-a6 movem.l d1-d7/a2-a6,144(a1) movem.l (a0)+,d1-d7/a2-a6 movem.l d1-d7/a2-a6,192(a1) lea 240(a1),a1 dbra d0,\copy \done: movem.l (sp)+,d0-a6 rte \skip: lea $70001d,a2 ; wait for vertical blank move.b (a2),d2 \vwl2: move.b (a2),d1 eor.b d2,d1 bpl.s \vwl2 move.w int_start(pc),$600016 ; restart timer for next frame clr.w (a1)+ addq.w #1,(a1) bra.s \done **************************************** Deactivate grayscale Deactivate_Grayscale: move.l $74,a6 move.l old_int_5(pc),$74 move.w #$980,$600010 move.w handle(pc),-(sp) JSR_ROM HeapFree addq.l #2,sp moveq #0,d0 trap #1 JSR_ROM PortRestore move.b #$1b,$600015 move.b #$b2,$600017 move.w #$3180,$600012 tst.w -(a6) beq.s \hw1 move.b #$cc,$600017 JSR_ROM OSContrastUp \hw1: rts **************************************** Wait for vertical blank * * This routine waits for the grayscale routine to acknowledge a vertical * blank. This will be signaled just before new data in plane 0 will be * shown for HW1. After HW2, it is signalled one vertical before plane0 * will be shown. Under either HW version, * The caller can safely rewrite plane 0 immediately after this returns, and * plane 1 simultaneously or later. * ******** Timer_Delay: Wait_VBL: lea vbl_count(pc),a6 clr.w (a6) \w: tst.w (a6) beq.s \w rts **************************************** Variables handle: dc.w 0 ; Handle for allocated memory gs_plane0: dc.l 0 gs_plane1: dc.l 0 gs_div8: dc.w 0 ; plane 0 / 8 (HW1 only) int_start: dc.w 0 ; count # of interrupts page_count: dc.w 0 ; Counts page being shown vbl_count: dc.w 0 ; Incremented each light plane show old_int_5: dc.l 0