$PAGINATE
$title(Arnold 5 test)
$subtitle(Support subroutines)
$copyright(Copyright (c) 1989, 1990, Amstrad plc.)
$pagewidth=131

;
; These routines are the general "firmware" support routines that do the
; mundane things like writing characters to the screen or printer etc.
;

        PUBLIC  ScreenSetMode
        PUBLIC  ScreenResetPalette
        PUBLIC  SetCursorPos
        PUBLIC  ScreenPrintChar
        PUBLIC  PrintAHex
        PUBLIC  LPrintAHex
        PUBLIC  HighlightLine
        PUBLIC  PrinterPrintChar
        PUBLIC  PrintStringHL
        PUBLIC  WriteAYChip
        PUBLIC  ProgramNote
        PUBLIC  KeyboardRead
        PUBLIC  KeyboardReadNoDelay
        PUBLIC  DelayASeconds
        PUBLIC  UnlockArn5
        PUBLIC  RelockArn5
        PUBLIC  OfferReTest


        EXTERN  ?KeyboardBuffer                 ;in TESTVARS
        EXTERN  ?KeysHeldDownBuffer             ;in TESTVARS
        EXTERN  ?PrintCharXORMask               ;in TESTVARS
        EXTERN  ?ScreenMode                     ;in TESTVARS
        EXTERN  .CRTCInitialValues              ;in MESSAGES
        EXTERN  .Initial16ColorPalette          ;in MESSAGES
        EXTERN  .InitProcessorMessPt1           ;in MESSAGES
        EXTERN  .MatrixDefinitions              ;in MESSAGES
        EXTERN  .HexDigits                      ;in MESSAGES
        EXTERN  .Arnold5Key                     ;in MESSAGES
        EXTERN  .RetestMess                     ;in MESSAGES


        include "equates.inc"

        DEFSEG  TestCode, CLASS=CODE

        SEG     TestCode

;=============
ScreenSetMode:
;=============
;
; This takes a value in A to decide which of the three "normal" screen modes
; the screen should be set into. The video RAM is also cleared at this time.
;
; As the God awful ULA is write only and the register controlling the screen
; mode also contains the enables for the upper and lower ROMs, I have to keep
; a register copy of the current setting in BC' - when the ULA mode is changed
; the value in C' is masked with the passed mode number and is then set to the
; new value.
;
; Next the fixed initialisation values are output to the 6845
;
; Finally, the video memory (at 0C000h) is cleared to 0
;
        and     003h                    ;mode must be 0, 1 or 2
        ld      (?ScreenMode),a
        exx                             ;get access to BC'
        ld      b,a                     ;store A somewhere safe
        ld      a,c                     ;need C' in A to do bit bashing
        and     0FCh                    ;clear bottom two mode bits
        or      b                       ;retrieve the desired from B'
        ld      c,a                     ;put it where it should be
        ld      b,07Fh                  ;it's always 7F ! (ULA I/O address)
        out     (c),c                   ;switch video mode
        exx                             ;hide BC' again

        ld      hl,.CRTCInitialValues   ;in MESSAGES (fixed data)
        ld      bc,0BC0Fh               ;BCXX is 6845 addr reg. 0F is top reg.
_SupSet6845loop:
        out     (c),c                   ;select register C
        ld      a,(hl)                  ;get value for that reg. from data
        inc     hl                      ;step on pointer for next time round
        inc     b                       ;BDXX is write data reg.
        out     (c),a                   ;write that data into selected reg.
        dec     b                       ;B points back to BCXX again
        dec     c                       ;going backwards thru registers
        jp      p,_SupSet6845loop       ;if not -ve register then do another

        ld      hl,0C000h               ;base of default screen memory
        ld      c,0
_SupClearScreen:
        ld      (hl),c
        inc     hl
        ld      a,h
        or      l
        jr      nz,_SupClearScreen

        ld      de,0
        call    SetCursorPos

        ld      hl,.InitProcessorMessPt1 ;"Arnold 5 diagnostics Version "
        ld      b,1                     ;screen
        call    PrintStringHL

        ld      a,Version               ;an EQU at the top of this file
        call    ScreenPrintChar         ;print the version digit to screen

        ld      a,'.'                   ;the intervening full stop
        call    ScreenPrintChar         ;to the screen

        ld      a,Mark                  ;an EQU at the top of this file
        call    ScreenPrintChar         ;print the mark digit to screen

        ret

;==================
ScreenResetPalette:
;==================
;
; This routine sets the ULA palette to 16 well known colors (as on a real
; Arnold). The border and PEN 0 (background) are Blue while PEN 1 (used for
; all text) is Bright Yellow. The other 15 pens are set the same as Arnold
; except that 14 and 15 don't flash - they are fixed at Sky Blue and Orange.
;
        ld      hl,.Initial16ColorPalette
        ld      a,(hl)
        ld      c,10h                   ;PR4 set means set border
        ld      b,07Fh
        out     (c),c                   ;select border register
        and     a,01Fh                  ;all colors are 0..32
        or      a,01000000b             ;top bits=010 means palette value
        out     (c),a                   ;set border color

        inc     hl                      ;now point at list of 16 pens
        ld      d,15                    ;a counter to count them down
_SupResetPalLoop:
        ld      a,(hl)                  ;get next color
        inc     hl                      ;step on thru list for next time
        ld      c,d                     ;use loop counter as register number
        out     (c),c                   ;select register
        and     01Fh                    ;make sure value (from (hl)) is in range
        or      01000000b               ;top=010 to set palette
        out     (c),a                   ;write that palette register
        dec     d                       ;backwards thru the colors !
        jp      p,_SupResetPalLoop      ;if not -ve yet then go round for more

        ret

;============
SetCursorPos:
;============
;
; Takes new value for row and column in D and E respectively. These are defined
; in characters (so row is always 0..24). Co-ordinates are 0 based with 0,0
; identifying the character in the top left.
;
        ld      a,d                     ;new row value to A
        exx                             ;get access to DE'
        ld      d,a                     ;so D' = new row value
        exx
        ld      a,e
        exx
        ld      e,a                     ;so E' = new column value
        exx
        ret

;===============
ScreenPrintChar:
;===============
;
; On entry to this routine A contains the number of the character to print
; (32..127). The definitions of these chars have been stolen from Arnold. Chars
; 0..31 and 128..255 do not exist so 32 is subtracted from the character number
; then it is multiplied by 8 to index into the matrix table. At this point HL
; points to the base of the character definition. Register B holds the screen
; mode that the character should be printed in. DE' hold the current cursor
; position.
;
; late change: "ScreenMode" not B holds the display mode
;
; If B=2 (MODE 2 - the "easy" one). Then the usual start of character address
; routine is performed:
;       start = 0C000h + (D' * 80d) + E'
; The character is then printed by moving (HL) to start, (HL+1) to start+800h,
; (HL+2) to start+1000h, (HL+3) to start+1800h... (HL+7) to start+3800h
;
; If B=1 (MODE 1) then the start address is still calculated as above. Then
; FOR I=0 TO 7
;  (start+(I*800h))   = ((HL) AND 0F0h)
;  (start+(I*800h)+1) = ((HL) AND 00Fh) < 4
; NEXT I
;
; If B=0 (MODE 0) then the start address is still calculated as above. Then
; FOR I= 0 TO 7
;  (start+(I*800h))   = ((HL) AND 0C0h)
;  (start+(I*800h)+1) = ((HL) AND 030h) < 2
;  (start+(I*800h)+2) = ((HL) AND 00Ch) < 4
;  (start+(I*800h)+3) = ((HL) AND 003h) < 6
; NEXT I
;
        cp      0Dh                     ;is character a carriage return ?
        jr      nz,_SupPrintNotCR
        exx
        ld      e,0                     ;set column back to left hand side
        exx
        ret

_SupPrintNotCR:
        cp      0Ah                     ;is character a line feed ?
        jr      nz,_SupPrintNotLF
        exx
        inc     d                       ;step row on by one
        exx
        ret


_SupPrintNotLF:
        cp      08                      ;is it a backspace ?
        jr      nz,_SupPrintNotBS
        exx
        ld      a,e
        dec     a
        jp      p,_SupBSOK              ;can't go -ve
        ld      a,0
_SupBSOK:
        ld      e,a
        exx
        ret

_SupPrintNotBS:
        cp      09                      ;is it a TAB character
        jr      nz,_SupPrintableChar
        exx
        ld      a,e
        add     8                       ;if yes then step cursor on 8
        and     0F8h                    ;and clear bottom 3 bits to make
        ld      e,a                     ;an 8 char boundary.
        exx
        ret

_SupPrintableChar:
        sub     32                      ;characters start at 32
        ld      l,a
        ld      h,0                     ;use hl for 16 bit maths
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;hl = hl * 8
        ld      de,.MatrixDefinitions
        add     hl,de                   ;index into table of matrices
        ld      a,(?ScreenMode)
        cp      0
        jp      z,_SupPrintMode0
        cp      1
        jp      z,_SupPrintMode1
_SupPrintMode2:
        exx                             ;access DE' (row,column)
        ld      a,d
        exx
        ld      d,a                     ;D=D' = row
        exx
        ld      a,e
        exx
        ld      e,a                     ;E=E' so DE=row,column
        ld      b,h                     ;store matrix source addr for a mo'
        ld      c,l
        ld      hl,0C000h               ;screen base address
        ld      a,d                     ;save row somewhere safe (in A)
        ld      d,0                     ;so DE = just column
        add     hl,de                   ;HL = 0C000h + column
        ex      de,hl                   ;save that result somewhere safe (DE)
        ld      h,0
        ld      l,a                     ;so HL=row number
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;so HL=row*10h
        ex      de,hl                   ;so HL=0C000h+col and DE=row*10h
        add     hl,de                   ;so HL=0C000h+col+(row*10h) and DE=row*10h
        ex      de,hl                   ;so HL=row*10h and DE=0C000h+col+(row*10h)
        add     hl,hl                   ;so HL=20h*row
        add     hl,hl                   ;so HL=40h*row
        add     hl,de                   ;so HL=0C000h+col+(row*10h)+(40h*row)
                                        ;     =0C000h + row * 80d + col
        ex      de,hl                   ;so DE=all that (address in screen)
        ld      h,b
        ld      l,c                     ;so HL= source address in matrix
        ld      c,8                     ;8 screen lines
_SupMode2NextLine:
        push    bc
        ld      c,(hl)                  ;get byte of matrix
        ld      a,(?PrintCharXORMask)
        xor     c
        pop     bc
        ld      (de),a                  ;print it on screen!!
        inc     hl                      ;step on to next byte of matrix
        ld      a,d
        add     8                       ;step destination down a screen line
        ld      d,a
        dec     c
        jr      nz,_SupMode2NextLine
        jp      _SupPrintStepCursor

_SupPrintMode1:
        exx                             ;access DE' (row,column)
        ld      a,d
        exx
        ld      d,a                     ;D=D' = row
        exx
        ld      a,e
        exx
        ld      e,a                     ;E=E' so DE=row,column
        ld      b,h                     ;store matrix source addr for a mo'
        ld      c,l
        ld      hl,0C000h               ;screen base address
        ld      a,d                     ;save row somewhere safe (in A)
        ld      d,0                     ;so DE = just column
        add     hl,de                   ;HL = 0C000h + column
        add     hl,de                   ;HL = 0C000h + (column*2) (2 bytes/char)
        ex      de,hl                   ;save that result somewhere safe (DE)
        ld      h,0
        ld      l,a                     ;so HL=row number
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;so HL=row*10h
        ex      de,hl                   ;so HL=0C000h+col and DE=row*10h
        add     hl,de                   ;so HL=0C000h+col+(row*10h) and DE=row*10h
        ex      de,hl                   ;so HL=row*10h and DE=0C000h+col+(row*10h)
        add     hl,hl                   ;so HL=20h*row
        add     hl,hl                   ;so HL=40h*row
        add     hl,de                   ;so HL=0C000h+col+(row*10h)+(40h*row)
                                        ;     =0C000h + row * 80d + col
        ex      de,hl                   ;so DE=all that (address in screen)
        ld      h,b
        ld      l,c                     ;so HL= source address in matrix
        ld      c,8                     ;8 screen lines
_SupMode1NextLine:
;        ld      a,(hl)                  ;get matrix entry
        push    bc
        ld      c,(hl)                  ;get byte of matrix
        ld      a,(?PrintCharXORMask)
        xor     c
        pop     bc
        and     0F0h                    ;just want top two bits
        ld      (de),a                  ;store first of 4 bytes
        inc     de                      ;point at next screen byte

;        ld      a,(hl)                  ;get same matrix entry
        push    bc
        ld      c,(hl)                  ;get byte of matrix
        ld      a,(?PrintCharXORMask)
        xor     c
        pop     bc
        and     00Fh                    ;get 0000XXXX
        sla     a
        sla     a
        sla     a
        sla     a                       ;make XXXX0000
        ld      (de),a

        dec     de                      ;now make de point at first byte again

        inc     hl                      ;step on to next byte of matrix
        ld      a,d
        add     8                       ;step destination down a screen line
        ld      d,a
        dec     c
        jr      nz,_SupMode1NextLine
        jp      _SupPrintStepCursor

_SupPrintMode0:
        exx                             ;access DE' (row,column)
        ld      a,d
        exx
        ld      d,a                     ;D=D' = row
        exx
        ld      a,e
        exx
        ld      e,a                     ;E=E' so DE=row,column
        ld      b,h                     ;store matrix source addr for a mo'
        ld      c,l
        ld      hl,0C000h               ;screen base address
        ld      a,d                     ;save row somewhere safe (in A)
        ld      d,0                     ;so DE = just column
        add     hl,de                   ;HL = 0C000h + column
        add     hl,de                   ;HL = 0C000h + column*2
        add     hl,de                   ;HL = 0C000h + column*3
        add     hl,de                   ;HL = 0C000h + column*4 (4 bytes/char)
        ex      de,hl                   ;save that result somewhere safe (DE)
        ld      h,0
        ld      l,a                     ;so HL=row number
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;so HL=row*10h
        ex      de,hl                   ;so HL=0C000h+col and DE=row*10h
        add     hl,de                   ;so HL=0C000h+col+(row*10h) and DE=row*10h
        ex      de,hl                   ;so HL=row*10h and DE=0C000h+col+(row*10h)
        add     hl,hl                   ;so HL=20h*row
        add     hl,hl                   ;so HL=40h*row
        add     hl,de                   ;so HL=0C000h+col+(row*10h)+(40h*row)
                                        ;     =0C000h + row * 80d + col
        ex      de,hl                   ;so DE=all that (address in screen)
        ld      h,b
        ld      l,c                     ;so HL= source address in matrix
        ld      c,8                     ;8 screen lines
_SupMode0NextLine:
        ld      a,(hl)                  ;get matrix entry
        and     0C0h                    ;just want top two bits (XX000000)
        ld      (de),a                  ;store first of 4 bytes
        inc     de                      ;point at next screen byte

        ld      a,(hl)                  ;get same matrix entry
        and     030h                    ;get  00XX0000
        sla     a
        sla     a                       ;make XX000000
        ld      (de),a
        inc     de                      ;point at next screen byte

        ld      a,(hl)                  ;get same matrix entry
        and     00Ch                    ;get  0000XX00
        sla     a
        sla     a
        sla     a
        sla     a                       ;make XX000000
        ld      (de),a
        inc     de                      ;point at next screen byte

        ld      a,(hl)                  ;get same matrix entry
        and     003h                    ;get  000000XX
        sla     a
        sla     a
        sla     a
        sla     a
        sla     a
        sla     a                       ;make XX000000
        ld      (de),a

        dec     de                      ;now make de point at first byte again
        dec     de
        dec     de

        inc     hl                      ;step on to next byte of matrix
        ld      a,d
        add     8                       ;step destination down a screen line
        ld      d,a
        dec     c
        jr      nz,_SupMode0NextLine

_SupPrintStepCursor:
        exx                             ;as we have printed on the scren we
                                        ;should step the cursor on one char
        inc     e
        exx

        ret


;=========
PrintAHex:
;=========
;
; This is just the old classic that prints the contents of the A register
; as 2 hex digits. I'm gonna make a big assumption and use the stack.!
;
        push    af
        push    de
        push    hl
        push    bc

        push    af                      ;save lower nybble
        and     0F0h
        srl     a
        srl     a
        srl     a
        srl     a                       ;move upper to lower nybble
        call    _SupPrintANybble
        pop     af

        and     00Fh
        call    _SupPrintANybble

        pop     hl
        pop     de
        pop     bc
        pop     af
        ret

_SupPrintANybble:
        ld      hl,.HexDigits           ;string "0123456789ABCDEF"
        ld      e,a
        ld      d,0
        add     hl,de                   ;index into that string
        ld      a,(hl)                  ;pick up character to print
        jp      ScreenPrintChar
        
;==========
LPrintAHex:
;==========
;
; This is just the old classic that prints the contents of the A register
; as 2 hex digits. I'm gonna make a big assumption and use the stack.!
;
;
        push    af
        push    de
        push    hl
        push    bc

        push    af                      ;save lower nybble
        and     0F0h
        srl     a
        srl     a
        srl     a
        srl     a                       ;move upper to lower nybble
        call    _SupLPrintANybble
        pop     af

        and     00Fh
        call    _SupLPrintANybble

        pop     hl
        pop     de
        pop     bc
        pop     af
        ret

_SupLPrintANybble:
        ld      hl,.HexDigits           ;string "0123456789ABCDEF"
        ld      e,a
        ld      d,0
        add     hl,de                   ;index into that string
        ld      a,(hl)                  ;pick up character to print
        jp      PrinterPrintChar
        


;=============
HighlightLine:
;=============
;
; Uses the value in D which is a character line number and then goes along the
; 80 bytes of each of the 8 scan lines that that refers to and does an XOR on
; them with 0FFH to invert them (probably best to only use it in MODE 2!).
;
; A second call referring to the same line will obviously remove the highlight
;
        exx                             ;BC' has current ROM state etc
        ld      b,07Fh
        ld      a,c
        or      8                       ;set the upper ROM off bit
        out     (c),a                   ;so the upper ROM is now off
        exx

        push    de
        ld      h,0
        ld      l,d                     ;use HL for 16 bit sums
        add     hl,hl
        add     hl,hl
        add     hl,hl
        add     hl,hl                   ;make HL = char row * 10h
        ld      b,h
        ld      c,l                     ;take a copy of row*10h in BC
        add     hl,hl
        add     hl,hl                   ;so HL = row * 40h
        add     hl,bc                   ;now HL = row * 50h (i.e. * 80)
        ld      bc,0C000h               ;screen base address
        add     hl,bc                   ;HL = 0C000h + row * 80h
        ld      b,8                     ;gonna XOR 8 scan lines worth
_SupHighLineLoop:
        ld      c,80                    ;count of bytes per line
_SupHighByteLoop:
        ld      a,(hl)
        xor     0FFh                    ;flip the bits
        ld      (hl),a
        inc     hl                      ;step on a byte in this line
        dec     c
        jr      nz,_SupHighByteLoop
        ld      de,800h - 80
        add     hl,de                   ;step down a screen line
        dec     b
        jr      nz,_SupHighLineLoop
        pop     de

        exx
        out     (c),c                   ;set ROM state back as it was
        exx
        ret

;================
PrinterPrintChar:
;================
;
; Takes the value in A (0..255) and outputs it to the Centronics port.
; First a check is made on bit 0 of L' which is a flag bit that says whether
; a printer exists. Initially this says it does but if this routine timesout
; while waiting to see the BUSY signal go away then the bit is cleared and
; subsequent attempts to print via this routine are ignored (if the user
; wants he can set L':0 to retry.
;
; If the routine cannot print the character it returns C. It sets Z if the
; reason is that L':0=0 and NZ if the BUSY test timed out.
;
        ld      l,a                     ;save char to print somewhere safe
        exx
        bit     0,l                     ;check printer present bit in L'
        exx
        jr      nz,_SupGotPrinter
_SupNoPrinter:
;
; Z is set when we get here
;
        scf                             ;indicate no printing happened
        ret

_SupGotPrinter:
        ld      de,03200h               ;the timeout period (as per Arnold)
_SupWaitNoBUSY:
        ld      bc,0F500h               ;8255 port B (bit 6 = BUSY)
        in      a,(c)                   ;read the state of BUSY
        bit     6,a
        jr      z,_SupNotBUSY
        dec     de
        ld      a,d
        or      e
        jr      nz,_SupWaitNoBUSY
        exx
        res     0,l                     ;L':0=0 means printer not there
        exx

        xor     a                       
        inc     a                       ;make NZ
        scf                             ;indicate no printing happened
        ret

_SupNotBUSY:
;
; get here when BUSY went away within the timeout period. Next we must get
; the current setting of 6845 register 12. We then set or reset bit 3
; depending on whether the top bit of the char to print is set or not.
;
        ld      bc,0BC0Ch               ;gonna access 6845 register 12
        out     (c),c                   ;make reg. selection
        ld      b,0BFh                  ;addr. of 6845 input data reg.
        in      a,(c)                   ;get current state of reg 12

        bit     7,l                     ;is top bit of char to print set ?
        jr      z,_SupNoPrintTop
;
; Top bit is set so we must set bit 3 in 6845 register 12 which controls D7
;
        set     3,a                     ;set the printer D7 signal high
        jr      _SupPrintTopSet

_SupNoPrintTop:
;
; Top bit is clear so we must reset bit 3 in 6845 register 12.
;
        res     3,a

_SupPrintTopSet:
        ld      b,0BDh                  ;addr of 6845 output data reg.
        out     (c),a                   ;write value of reg 12 back again

;
; Now that the D7 magic has been performed we can do the bog standard
; Centronics print routine which consists of outputting D6..D0 to EFxx
; then setting bit 7 in EFxx (to STROBE the char) and finally resetting it.
; i.e.
;            STROBE
;              |D6....D0
;              ||     |
;       EFxx = 0XXXXXXX
;       EFxx = 1XXXXXXX
;       EFxx = 0XXXXXXX
;
        ld      a,l                     ;get char to print
        and     07Fh                    ;mask out top bit (clear STROBE)
        ld      b,0EFh                  ;I/O addr of Centronics is EFxx
;        di
        out     (c),a                   ;set data byte into latch
        or      080h                    ;set top bit (STROBE active)
        out     (c),a                   ;print the character
        and     07Fh                    ;clear STROBE bit again
        out     (c),a                   ;make port idle again.
;        ei
        xor     a                       ;clear carry to indicate success
        ret


;=============
PrintStringHL:
;=============
;
; This is the routine that is called to print a sting of characters pointed
; to by HL. The string is terminated with a "$" character.
;
; The B register indicates the destination of the output. B:0 set means output
; to the screen while B:1 set means output to the printer. The two may be used
; together.
;
; On exit HL points at the terminating "$".
;
        ld      a,h
        exx
        ld      h,a
        exx
        ld      a,l
        exx
        ld      b,a                     ;copy of HL in HB'
        exx
        ld      a,(hl)
        cp      '$'                     ;check for terminating "$"
        ret     z

        bit     0,b                     ;B:0=1 => print to screen
        jr      z,_SupPrintHLTryPrinter

        ex      af,af'
        ld      a,b                     ;store B in A'
        ex      af,af'
        call    ScreenPrintChar         ;print the char on screen at (D',E')
        ex      af,af'
        ld      b,a                     ;recover stored B
        ex      af,af'
        exx
        ld      a,h
        exx
        ld      h,a
        exx
        ld      a,b
        exx
        ld      l,a                     ;get HL back from HB'
        ld      a,(hl)                  ;get back char to print

_SupPrintHLTryPrinter
        bit     1,b                     ;B:1=1 => print to printer
        jr      z,_SupPrintHLNoPrinter

        ex      af,af'
        ld      a,b                     ;store B in A'
        ex      af,af'
        call    PrinterPrintChar
        ex      af,af'
        ld      b,a                     ;recover stored B
        ex      af,af'

_SupPrintHLNoPrinter:
        exx
        ld      a,h
        exx
        ld      h,a
        exx
        ld      a,b
        exx
        ld      l,a                     ;get HL back from HB'
        inc     hl                      ;step along the string
        jr      PrintStringHL

;===========
WriteAYChip:
;===========
;
; This routine will write the value in the E register to the AY register
; identified by the value in D.
;
        ld      bc,0F782h               ;port F7xx=8255 control. 82=AoBiCo
        out     (c),c                   ;make A Output, B Input, C Output
        ld      a,d                     ;get required AY reg. number
        ld      b,0F4h                  ;8255 port A (AY data lines)
        out     (c),a                   ;send selected AY reg. num.

        ld      b,0F6h                  ;8255 port C (AY control, key row, etc)
        in      a,(c)                   ;get state of port C
        or      0C0h                    ;set top 2 bits (AY: BDIR, BC1 = 11)
        out     (c),a                   ;latch the AY reg num into AY chip
        and     03Fh                    ;reset BDIR, BC1 (inactive)
        out     (c),a                   ;AY chip now has selected reg D
        ld      c,a                     ;rember state of BDIR, BC1 + key row etc

        ld      a,e                     ;the value to write to reg D
        ld      b,0F4h                  ;8255 port A (AY data lines)
        out     (c),a                   ;the data byte is now availble to AY
        ld      a,c                     ;get BDIR BC1 etc.
        or      080h                    ;make BDIR BC1 = 10 (write data)
        ld      b,0F6h                  ;8255 port C controls BDIR etc.
        out     (c),a                   ;BDIR BC1 = 10 (write it!)
        out     (c),c                   ;BDIR BC1 = 00 (back to inactive)
; YOIK !
        ret

;===========
ProgramNote:
;===========
;
; This routine is aimed at taking the misery out of programming the AY chip.
; It takes the following parameters:
;
; HL = period of note to be played
; B = which channels the tone period should be set into (B:0=A, B:1=B, B:2=C)
;
; The value in A is used to look up a tone period and this value is programmed
; into the AY register pairs 0/1, 2/3 and 4/5 according to which bits are set
; in B.
;
; The AY noise period register (6) will not be set. (This should be done before
; or after calling the routine if required).
;
; The enables register (7) will not be set. The caller should call this routine
; as many times as it takes to load up the AY chip and then set the channel
; (and perhaps noise) bits in R7 to start the channel(s) playing.
;
; The 3 amplitude control registers (8,9,10) will be set to 00Fh (max volume
; with no envelopes used). These could be updated after the routine is called
; if required (for example to fade out a note).
;
; Registers 11, 12 and 13 will not be touched as envelopes are not being used.
;
;
        bit     0,b                     ;program channel A ?
        jr      z,_SupNoSetAYA
        push    bc
        ld      d,0                     ;AY reg 0 is channel A tone (fine)
        ld      e,l
        call    WriteAYChip             ;set channel A fine tone
        ld      d,1                     ;AY reg 1 is channel A tone (coarse)
        ld      e,h
        call    WriteAYChip             ;set channel A coarse tone
        pop     bc

_SupNoSetAYA:
        bit     1,b                     ;program channel B ?
        jr      z,_SupNoSetAYB
        push    bc
        ld      d,2                     ;AY reg 2 is channel B tone (fine)
        ld      e,l
        call    WriteAYChip             ;set channel B fine tone
        ld      d,3                     ;AY reg 3 is channel B tone (coarse)
        ld      e,h
        call    WriteAYChip             ;set channel B coarse tone
        pop     bc

_SupNoSetAYB:
        bit     2,b                     ;program channel C ?
        jr      z,_SupNoSetAYC
        push    bc
        ld      d,4                     ;AY reg 4 is channel C tone (fine)
        ld      e,l
        call    WriteAYChip             ;set channel C fine tone
        ld      d,5                     ;AY reg 5 is channel C tone (coarse)
        ld      e,h
        call    WriteAYChip             ;set channel C coarse tone
        pop     bc

_SupNoSetAYC:
        ld      d,8                     ;channel A amplitude
        ld      e,00Fh                  ;max volume with no envelope
        call    WriteAYChip
        ld      d,9                     ;channel B amplitude
        ld      e,00Fh                  ;max volume with no envelope
        call    WriteAYChip
        ld      d,10                    ;channel C amplitude
        ld      e,00Fh                  ;max volume with no envelope
        call    WriteAYChip
        ret

;===================
KeyboardReadNoDelay:
;===================
;
; Normally I do a bit of a delay at the end of Keyboard Read to debounce but
; when running time critical code (e.g. PALET27) I want to get back as fast as
; possible.
;
        ld      a,0
        jr      _SupKeyContinueRead

;============
KeyboardRead:
;============
;
; This simple routine just outputs the values 0..9 to the 4 keyboard row
; select lines in 8255 port C. As each row in turn is made active the AY
; register 14 (8 bit I/O) is read to check the state of the column lines
; If a column is seen to be high (bit set) while a particular row is
; selected then the key at the intersection of that row and column must be
; being pushed at that time. The keynumber = (row * 8) + column
;
        ld      a,1
_SupKeyContinueRead:

        push    af                      ;save the delay y/n flag
        ld      hl,?KeyboardBuffer

        ld      bc,0F782h
        out     (c),c                   ;make 8255 port A=output
        ld      bc,0F40Eh
        out     (c),c                   ;selecting AY register 14
        ld      b,0F6h                  ;8255 port C has BDIR/BC1 bits
        in      a,(c)                   ;read current state
        or      0C0h                    ;set BDIR BC1 = 11 (select addr)
        out     (c),a                   ;latch 14 into AY addr select reg
        and     03Fh                    ;reset top two bits (BDIR,BC1 = 00)
        out     (c),a                   ;make AY inactive again
        ld      bc,0F792h
        out     (c),c                   ;make 8255 port A=input

        ld      d,9                     ;testing 10 rows (9, 8, ... 0)
_SupKeyRowLoop:
        ld      b,0F6h
        in      a,(c)                   ;get state of 8255 port C
        and     0F0h                    ;clear bottom 4 row select bits
        or      d                       ;replace them with value in D
        out     (c),a                   ;SELECT A ROW
        and     03Fh
        or      040h                    ;make BDIR BC1 = 01 (read from AY)

        ld      b,0F6h                  ;port C
        out     (c),a                   ;read from AY reg 14 to port A

        ld      b,0F4h                  ;8255 port A now has column values
        in      a,(c)                   ;READ THE COLUMNS
        ld      (hl),a                  ;store byte in buffer
        inc     hl
        dec     d                       ;next keyboard row
        jp      p,_SupKeyRowLoop        ;keep doing rows till we go thru 0
;
; now need to check buffer and return HL pointing to a list of key numbers
; being held down with A containing the first in the list.
;
        ld      hl,?KeyboardBuffer
        ld      de,?keysHeldDownBuffer
        ld      a,0FFh
        ld      (de),a                  ;start by assuming no key pressed
        ld      b,10                    ;10 bytes (one for each row)
        ld      c,0                     ;count of keys found
_SupKeyProcessBitsLoop:
        ld      a,(hl)
        inc     hl
        cp      0FFh
        jr      z,_SupNoKeyOnThisRow
        call    _SupKeyIsDown           ;convert
        ld      (de),a                  ;store in vector of key numbers
        inc     c                       ;count of keys found
        inc     de
_SupNoKeyOnThisRow:
        djnz    _SupKeyProcessBitsLoop
        ld      hl,?KeysHeldDownBuffer

        pop     af                      ;get delay flag (0/1)
        or      a
        jr      z,_SupKeyNoDelay

        push    bc
        ld      c,030h
_SupKeyDeBounceOuter:
        ld      b,0
_SupKeyDeBounceInner:
        djnz    _SupKeyDeBounceInner
        dec     c
        jr      nz,_SupKeyDeBounceOuter
        pop     bc

_SupKeyNoDelay:
        ld      a,(hl)                  ;first key found or 0FFh
        ret

_SupKeyIsDown:
;
; A column value has been read which is not 0FFh so at least 1 bit must be 0
; We now scan across the byte looking for the 0 bit (rotating into carry)
; When found the bit count is added to 8 times the current row (in D) to give
; a keynumber (0..79)
;
        push    de
        ld      e,8                     ;bit count
_SupKeyDownBitLoop:
        sla     a                       ;shift top bit into carry (0 to bot)
        jr      nc,_SupKeyBitFound
        dec     e
        jr      _SupKeyDownBitLoop      ;no conditional - it must be found

_SupKeyBitFound:
        ld      a,b
        dec     a                       ;cos rows are 0..9 not 1..10
        add     a,a
        add     a,a           
        add     a,a                     ;A = row * 8
        add     a,e                     ;A = row * 8 + column = keynumber
        dec     a                       ;cos E is 1..8 rather than 0..7
        pop     de
        ret

;=============
DelayASeconds:
;=============
;
; Delay for number of seconds given in A
;
; In one second there are 4,000,000 T-States
;
        push    af
        push    bc
        push    hl
        ld      h,0
        ld      l,a
        add     hl,hl
        add     hl,hl                   ;times A by 4 cos emulator told me
DelayMainLoop:
        ld      c,0F0h
DelayOuterLoop:
        ld      b,0
DelayInnerLoop:
        djnz    DelayInnerLoop
        dec     c
        jr      nz,DelayOuterLoop
        dec     hl
        ld      a,h
        or      l
        jr      nz,DelayMainLoop
        pop     hl
        pop     bc
        pop     af
        ret


;==========
UnlockArn5:
;==========
;
; Before an Arnold 5 feature can be accessed we must unlock the ASIC. The
; following does just that. The method is to send a fixed sequence of data
; to I/O addr BCxx (6845 address register).
;
; Next the new features I/O page is switched to appear at 04000h.
;
; The routine then makes a simple check to see if the new features have come
; alive by writing the upper nybble of the green register of sprites 0..15
; (addresses = 6423h, 6425h, 6427h, ... 643Fh) with non-zero values - reading
; them back and checking they are zeroised.
;
        ld      hl,.Arnold5Key
        ld      d,18
        ld      bc,0BC00h               ;6845 address register
_SupUnlockLoop:
        ld      a,(hl)                  ;a byte of the key
        out     (c),a                   ;output the sequence to 6845 addr reg
        inc     hl                      ;step along key
        dec     d
        jr      nz,_SupUnlockLoop

        ld      b,07Fh                  ;the ULA
        ld      c,10111000b             ;make ULA registers appear at 4000h
        out     (c),c

        ld      b,15                    ;there are 15 sprites
        ld      hl,06423h               ;address of 1st green reg to test
_SupUnlockGreenLoop:
        ld      a,l                     ;will go 23,25,...3F (always non-zero)
        and     a,00Fh                  ;only want lower nybble
        add     a,a
        add     a,a
        add     a,a
        add     a,a                     ;move to higher nybble
        ld      e,a                     ;save it for a sec'
        ld      a,(hl)                  ;get current green
        or      e                       ;add in the new top nybble
        ld      (hl),a                  ;try to set the new green
        ld      a,(hl)                  ;when read back D7..D4 should = 0
        inc     hl
        inc     hl
        and     0F0h                    ;clear low bits
        jr      nz,_SupUnlockFail
        djnz    _SupUnlockGreenLoop
        xor     a                       ;make NC
        ret

_SupUnlockFail:
        scf
        ret

;==========
RelockArn5:
;==========
;
; After testing/using an Arnold 5 feature I will re-lock it just to be on
; the safe side. This involves sending the first 15 bytes of the 16 byte key
; but then sending an out of sequence 16th byte
;
; We assume the register page is on at 4000h. So after re-locking we test
; locations 6401, 6403 and expect that we will read what we write. If not
; then perhaps the new features are stuck on.
;
        ld      b,07Fh                  ;ULA
        ld      c,10100000b             ;register page off
        out     (c),c

        ld      hl,.Arnold5Key
        ld      d,17
        ld      bc,0BC00h               ;6845 address register
_SupRelockLoop:
        ld      a,(hl)
        out     (c),a
        inc     hl
        dec     d
        jr      nz,_SupRelockLoop

        ld      a,0A5h                  ;it actually expects 0FFh
        out     (c),a

        ld      b,15                    ;there are 32 green entries
        ld      hl,06423h               ;address of 1st green reg to test
_SupRelockGreenLoop:
        ld      a,l                     ;will go 1,3,...3F (always non-zero)
        and     a,00Fh                  ;only want lower nybble
        add     a,a
        add     a,a
        add     a,a
        add     a,a                     ;move to higher nybble
        ld      e,a                     ;save it for a sec'
        ld      a,(hl)                  ;get current green
        or      e                       ;add in the new top nybble
        ld      (hl),a                  ;try to set the new green
        ld      a,(hl)                  ;when read back D7..D4 should = 0
        inc     hl
        inc     hl
        and     0F0h                    ;clear low bits
        jr      z,_SupRelockFail        ;we expected upper to be non-zero
        djnz    _SupRelockGreenLoop
        xor     a                       ;make NC
        ret

_SupRelockFail:
        scf
        ret

;===========
OfferRetest:
;===========
;
; This is called at the end of some tests. It just clears the screen and puts
; up a message saying "Press ^ to re run the test,
;                      any other key to continue"
; If the user presses ^ then we RET C else we RET NC
;
        ld      a,1
        call    ScreenSetMode

        ld      de,00A00h
        call    SetCursorPos

        ld      hl,.RetestMess
        ld      b,1
        call    PrintStringHL

        ld      a,1
        call    DelayASeconds           ;keyboard de-bounce

        ld      b,100
RetestWaitKey:
        push    bc
        call    KeyboardRead
        pop     bc
        cp      0FFh
        jr      nz,RetestGotKey
        djnz    RetestWaitKey

RetestGotKey:
        cp      72
        jr      z,DoRetest
        cp      48
        jr      z,DoRetest
        cp      0
        jr      z,DoRetest

        ld      a,1
        call    ScreenSetMode
        xor     a                       ;make NC cos no retest
        ret

DoRetest:
        ld      a,1
        call    ScreenSetMode
        scf                             ;make C for a retest
        ret

        END

