; Copyright 1996 Acorn Computers Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;
; > $.Source.PMF.i2cutils

; Authors JBiggs (m2), PFellows, TDobson, AGodwin

; ***********************************
; ***    C h a n g e   L i s t    ***
; ***********************************

; Date       Name  Description
; ----       ----  -----------
; 28-Mar-95  JRH   Added support for E2ROMs and/or CMOS, conditioned on
;                  E2ROMSupport which is defined elsewhere
;                  Uses RTCFitted and NVRamSize in KernelWS
; 03-Jul-96  JRH   Took out code conditioned on :LNOT: NewClockChip
;                  Fixed support for E2ROM. E2 works in the same gross way as
;                  CMOS. Any E2 fitted > 256 bytes will not be accessed by these
;                  routines.
; 07-Dec-96  AMG   Renaissance. Leave this file as is, allowing the E2ROMSupport
;                  switch to disable non-STB bits
; 12-Jun-97  TMD   (Really) fix OTP access problem.
; 17-Sep-98  KJB   Add support for 16K 24C128 EEPROM.
; 21-Sep-98  KJB   Add OS_NVMemory SWI.
; 30-Jul-99  KJB   Add support for 8K 24C64 EEPROM.
; 23-Sep-99  KJB   Remove support for 24C64, add support for 4K and 8K protectable ATMEL parts.

PhysChecksum            *       (((CheckSumCMOS + &30) :MOD: &F0) + &10)

; Device addresses
RTCAddressPHI           *       &a0     ; Philips RTC + 240 byte CMOS
E2ROMAddress2K          *       &e0     ; 24C174 device - 2K
E2ROMAddress2K_OTP      *       &60     ; 24C174 device - OTP section
E2ROMAddress4K          *       &a4     ; 24C32 device - 4K (top 1K protectable)
E2ROMAddress8K_prot     *       &a2     ; 24C64 device - 8K (top 2K protectable)
E2ROMAddress8K          *       &ae     ; 24C64 device - 8K
E2ROMAddress16K         *       &a8     ; 24C128 device - 16K
E2ROMAddress32K         *       &a6     ; 24CS256 device - 32K (top 2K possibly OTP)


; *****************************************************************************
;
;       HexToBCD - Convert byte in hex to BCD
;
; in:   R0 = byte in hex
;
; out:  R0 = byte in BCD (ie R0 := (R0 DIV 10)*16 + R0 MOD 10)
;       All other registers preserved
;

HexToBCD ROUT
        Push    "R1,R2, R14"
        MOV     R1, #10
        DivRem  R2, R0, R1, R14                 ; R2=R0 DIV 10; R0=R0 MOD 10
        ADD     R0, R0, R2, LSL #4
        Pull    "R1,R2, PC"

; *****************************************************************************
;
;       BCDToHex - Convert byte in BCD to hex
;
; in:   R0 = byte in BCD (ie x*16 + y)
;
; out:  R0 = byte in hex (ie x*10 + y)
;       All other registers preserved
;

BCDToHex ROUT
        Push    "R14"
        MOV     R14, R0, LSR #4                 ; R14 := x
        ADD     R14, R14, R14, LSL #1           ; R14 := x*3
        SUB     R0, R0, R14, LSL #1             ; R0 := R0 - x*6 = x*10
        Pull    "PC"

; *****************************************************************************
;
;       HTBSR9 - hex to BCD and store at "next free byte" R9
;
HTBS9   ROUT
        Push    R14
        BL      HexToBCD
        STRB    R0, [R9], #1
        Pull    PC

; *****************************************************************************
;
;       Write - Write a byte of CMOS RAM specified by logical address
;
; in:   R0 = address in CMOS RAM
;       R1 = data
;
; out:  All registers preserved
;

WriteWithError ROUT
        Push    "R0-R4, R14"
        BL      MangleCMOSAddress
        BCC     %FT05

        ADD     R13, R13, #4            ; junk stacked R0
        ADR     R0, ErrorBlock_CoreNotWriteable
 [ International
        BL      TranslateError
 |
        SETV
 ]
        Pull    "R1-R4,PC"

        MakeErrorBlock CoreNotWriteable

WriteWithoutProtection                  ; allowing write to "OTP" and protected section
        Push    "R0-R4, R14"
        BL      MangleCMOSAddress
        Pull    "R0-R4, PC", CS         ; if invalid, then exit
        MOV     R2, R0
        MOV     R3, R1
        CMP     R0, #&10
        MOVLO   R4, #&1000000           ; don't change checksum for OTP
        BLO     %FT10
        B       %FT08                   ; do change checksum for protected region

Write
        Push    "R0-R4, R14"
        BL      MangleCMOSAddress
        Pull    "R0-R4, PC", CS         ; if invalid, then exit
05
  [ E2ROMSupport
        CMP     r0, #&10
        Pull    "R0-R4, PC", CC         ; don't write to OTP section
  ]

  [ E2ROMSupport
        LDR     R14, =ZeroPage          ; don't write to protected section
        LDRB    R14, [R14, #NVRamWriteSize]
        CMP     R0, R14, LSL #8         ; (note assumption that NVRamWriteSize is
        Pull    "R0-R4, PC", HS         ; outside mangled region).
  ]

        MOV     R2, R0
        MOV     R3, R1
08
 [ ChecksumCMOS
        BL      ReadStraight            ; calculate new checksum :
        MOV     R4, R0
        TEQ     R4, R3                  ; don't bother with write if
        Pull    "R0-R4, PC", EQ         ; oldcontents == newcontents

        MOV     R0, #PhysChecksum
        BL      ReadStraight
        SUB     R0, R0, R4              ; = oldsum - oldcontents
        ADD     R4, R0, R3              ;          + newcontents

        AND     R4, R4, #&FF
        CMPS    R2, #PhysChecksum       ; don't write new checksum ...
        ORREQ   R4, R4, #&1000000       ; if checksum is being written
 ]
10
        CMP     r2, #&100               ; check small cache limit
        BCS     %FT15
        LDR     R1, =ZeroPage+CMOSRAMCache      ; update cache, but always write to
        STRB    R3, [R1, R2]            ; real hardware as well
15

  [ HAL
        Push    "R2,R3,sb,R12"
        AddressHAL
        CallHAL HAL_NVMemoryType
        AND     R0, R0, #NVMemoryFlag_Provision
        TEQ     R0, #NVMemoryFlag_None

        ; If there's no NVmemory, all we have is the internal cache.
        Pull    "R2,R3,sb,R12", EQ
        Pull    "R0-R4,PC", EQ

        TEQ     R0, #NVMemoryFlag_HAL
        BNE     %FT20                   ; Go and do IIC stuff.

        ; Make the HAL call - we have to write the data into a buffer.
        Pull    "R0"
        STRB    R3, [sp, #-4]!
        MOV     R1, sp
        MOV     R2, #1
        CallHAL HAL_NVMemoryWrite
        TST     R4, #&1000000
        BNE     %FT18
        LDR     R1, =ZeroPage+CMOSRAMCache
        STRB    R4, [R1, #PhysChecksum]
        STRB    R4, [sp]
        MOV     R0, #PhysChecksum
        MOV     R1, sp
        MOV     R2, #1
        CallHAL HAL_NVMemoryWrite
18
        ADD     sp, sp, #4
        Pull    "R3,sb,R12"
        Pull    "R0-R4,PC"
20
        Pull    "R2,R3,sb,R12"
  ]


 [ E2ROMSupport
        MOV     R0, R2
        BL      GetI2CAddress           ; convert to device address + offset
        MOV     R2, R0                  ; save the offset
 |
        MOV     R1, #RTCAddressPHI
 ]


        AND     R0, R1, #&FF            ; device address for write
        ORR     R0, R0, #1:SHL:29       ; retry
        TST     R1, #&100               ; NE if two byte offset
        SUB     R13, R13, #4
        MOV     R14, R13
        MOVNE   R1, R2, LSR #8
        STRNEB  R1, [R14], #1           ; offset (MSB)
        STRB    R2, [R14], #1           ; offset (LSB)
        STRB    R3, [R14], #1           ; data
        MOV     R1, R13
        SUB     R2, R14, R13
        BL      IIC_Op
        ADD     R13, R13, #4

        [ ChecksumCMOS
        TST     R4, #&1000000           ; loop again to write new checksum
        MOV     R3, R4
        MOV     R2, #PhysChecksum
        ORR     R4, R4, #&1000000       ; but ensure it only happens once
        BEQ     %BT10
        ]
        Pull    "R0-R4, PC"

; *****************************************************************************
;
;       WriteBlock - Write a block of CMOS RAM specified by logical address
;
; in:   R0 = address in CMOS RAM
;       R1 = address to copy from
;       R2 = length
;
; out:  All registers preserved
;


WriteBlock ROUT
        Push    "R0-R4,R14"
  [     E2ROMSupport
        LDR     R14, =ZeroPage
        LDRB    R4, [R14, #NVRamWriteSize]
        LDRB    R14, [R14, #NVRamSize]
        MOV     R4, R4, LSL #8
        MOV     R14, R14, LSL #8
  |
        MOV     R14, #240
        MOV     R4, R14
  ]

        CMP     R0, R14
        BHS     %FT90

        ADDS    R3, R0, R2              ; R3 = end address - check unsigned overflow
        BCS     %FT90
        CMP     R3, R14
        BHI     %FT90

        CMP     R0, R4                  ; ignore writes totally outside writable area
        BHS     %FT80

        SUBS    R14, R3, R4
        SUBGT   R2, R2, R14             ; truncate writes partially outside writable area

        TEQ     R2, #0
        BEQ     %FT80

        CMP     R0, #CheckSumCMOS       ; are we going to write the checksum byte?
        BHI     %FT03
        CMP     R3, #CheckSumCMOS
        BHI     %FT05

03
; we're not writing the checksum byte manually, so we need to update it
        MOV     R4, R1
        MOV     R1, #0
        BL      ChecksumBlock           ; find the checksum of what we're about to
        ORR     R3, R1, #&80000000      ; overwrite
        MOV     R1, R4
        B       %FT08

05      MOV     R3, #0
08      MOV     R4, #0
10      BL      WriteSubBlock
        BVS     %FT80
        TEQ     R2, #0
        BNE     %BT10

        TST     R3, #&80000000          ; were we going to write the checksum?
        BEQ     %FT80

        MOV     R0, #CheckSumCMOS
        BL      Read                    ; get old checksum byte
        ADD     R0, R0, R4              ; add new data checksum
        SUB     R1, R0, R3              ; subtract old checksum
        MOV     R0, #CheckSumCMOS
        BL      Write                   ; write back new checksum

80
        Pull    "R0-R4,PC"

90
        ADD     SP, SP, #4              ; junk stacked R0
        ADR     R0, ErrorBlock_CoreNotWriteable
 [ International
        BL      TranslateError
 |
        SETV
 ]
        Pull    "R1-R4,PC"

; *****************************************************************************
;
;       WriteSubBlock - Write a block of CMOS RAM specified by logical address.
;                       Assumes the address is valid, and will only read as much
;                       as it can in a single IIC transaction.
;
; in:   R0 = address in CMOS RAM
;       R1 = address to copy from
;       R2 = length
;
; out:  R0-R2 updated to reflect the amount written.
;       R4 incremented by sum of bytes written.
;
WriteSubBlock ROUT
        Push    "R3,R5-R6,R14"
        MOV     R6, R4
; establish end of the current contiguous block, and the logical->physical address offset.
        CMP     R0, #1                  ; 00 -> 40 uncached
        MOVLO   R3, #1
        MOVLO   R4, #&40-&00
        BLO     %FT10
        CMP     R0, #&C0                ; [01..C0) -> [41..100) cached
        MOVLO   R3, #&C0
        MOVLO   R4, #&41-&01
        BLO     %FT10
        CMP     R0, #&F0                ; [C0..F0) -> [10..40) cached
        MOVLO   R3, #&F0
        MOVLO   R4, #&10-&C0
        BLO     %FT10
        CMP     R0, #&100
        ADDHS   R3, R0, R2              ; [100..) -> [100..) uncached
        MOVHS   R4, #0
        BHS     %FT10

; [F0..100) -> not written
        MOV     R3, #&100
        ADD     R14, R0, R2
        CMP     R3, R14
        MOVHI   R3, R14
        SUB     R14, R3, R0
        ADD     R0, R0, R14
        ADD     R1, R1, R14
        SUB     R2, R2, R14
        Pull    "R3,R5-R6,PC"

; R3 = logical end of current segment (exclusive)
; R4 = offset from logical to physical address for this segment
10
        ADD     R14, R0, R2
        CMP     R3, R14
        MOVHI   R3, R14
 [ E2ROMSupport
; R3 = logical end of possible transaction (exclusive). Now check we don't cross page boundaries.
        LDR     R14, =ZeroPage
        LDRB    R14, [R14, #NVRamPageSize]
        MOV     R5, #1
        MOV     R14, R5, LSL R14        ; R14 = (1<<pagesize)

        ADD     R5, R0, R4              ; R5 = physical start address
        ADD     R5, R5, R14
        SUB     R14, R14, #1
        BIC     R5, R5, R14             ; R5 = physical end of page with start address in
        SUB     R5, R5, R4              ; R5 = logical end of page with start address in

        CMP     R5, R3
        MOVLO   R3, R5                  ; adjust R3 to not cross page boundary
 ]

        CMP     R0, #&100               ; check it's a cacheable segment
        BHS     %FT15

        LDR     R14, =ZeroPage+CMOSRAMCache
        Push    "R3, R4"
        ADD     R3, R3, R4              ; R3 = physical end address
        ADD     R4, R4, R0              ; R4 = physical address
        ADD     R3, R3, R14             ; R3 = cache end address
        ADD     R4, R4, R14             ; R4 = cache address
        SUB     R14, R3, R4             ; R14 = bytes being written
        MOV     R5, R1                  ; remember R1
12      LDRB    R14, [R1], #1           ; update cache copy
        STRB    R14, [R4], #1
        CMP     R4, R3
        BLO     %BT12
        MOV     R1, R5                  ; restore R1, and continue to update real memory
        Pull    "R3, R4"
15
        Push    "R0-R2"
        ADD     R0, R0, R4              ; R0 = physical address
  [ HAL
        Push    "sb, R12"
        MOV     R5, R0                  ; save address
        AddressHAL
        CallHAL HAL_NVMemoryType
        AND     R0, R0, #NVMemoryFlag_Provision
        TEQ     R0, #NVMemoryFlag_None

        ; If there's no NVmemory, tough - we just return.
        Pull    "sb,R12", EQ
        Pull    "R0-R2", EQ
        MOVEQ   R2, #0                  ; nothing written
        Pull    "R3,R5-R6,PC", EQ

        TEQ     R0, #NVMemoryFlag_HAL
        MOV     R0, R5                  ; restore address
        BNE     %FT17                   ; do IIC things.

        ; Make the HAL call
        CallHAL HAL_NVMemoryWrite       ; returns bytes wrtten in R0
        MOV     R5, R0
        Pull    "sb,R12"

        Pull    "R0-R2"

        ADD     R0, R0, R5
        SUB     R2, R2, R5

16      SUBS    R5, R5, #1              ; update checksum
        LDRCSB  R14, [R1], #1
        ADDCS   R6, R6, R14
        BCS     %BT16
        MOV     R4, R6

        Pull    "R3,R5-R6,PC"

17
        Pull    "sb,R12"
  ]

  [     E2ROMSupport
        BL      GetI2CAddress           ; convert to device address and offset
  |
        MOV     R1, #RTCAddressPHI
  ]

        MOV     R2, R0                  ; save the offset
        SUB     R13, R13, #12*2+4
        MOV     R14, R13
        TST     R1, #&100               ; 2-byte address?
        MOVNE   R0, R2, LSR #8
        STRNEB  R0, [R14], #1           ; offset (MSB)
        STRB    R2, [R14], #1           ; offset (LSB)

        SUB     R14, R14, R13
        STR     R14, [R13, #12]         ; transfer 1 length

        AND     R14, R1, #&FF
        ORR     R0, R14, #1:SHL:29      ; (retry)
        STR     R0, [R13, #4]           ; transfer 1 address
        ORR     R14, R14, #1:SHL:31     ; (no repeated start)
        STR     R14, [R13, #16]         ; transfer 2 address
        STR     R13, [R13, #8]          ; transfer 1 data

        ADD     R14, R13, #12*2+4
        LDMIA   R14, {R0-R2}
        SUB     R5, R3, R0              ; R5 = bytes being written
        ADD     R0, R0, R5              ; update return R0
        SUB     R2, R2, R5              ; update return R2
        STMIB   R14, {R0,R2}

        STR     R1, [R13, #20]          ; transfer 2 data
        STR     R5, [R13, #24]          ; transfer 2 length
        ADD     R0, R13, #4
        MOV     R1, #2
        BL      IIC_OpV

        LDR     R1, [R13, #20]          ; recover data pointer
        ADD     R13, R13, #12*2+4+4
20
        LDRB    R0, [R1], #1
        SUBS    R5, R5, #1
        ADD     R6, R6, R0              ; update checksum counter
        BNE     %BT20
                                        ; V clear
        MOV     R4, R6

        Pull    "R0,R2,R3,R5,R6,PC"

; *****************************************************************************
;
;       Read - Read a byte of CMOS RAM specified by logical address
;       ReadStraight - Read a byte of CMOS RAM specified by physical address
;       ReadWithError - Read a byte of CMOS RAM specified by logical address, giving error if out of range
;
; in:   R0 = address in CMOS RAM
;
; out:  R0 = data (illegal address return 0, or error for ReadWithError)
;       All other registers preserved
;

ReadStraight ROUT
        Push    "R1,R2,R14"
        B       %FT10

ReadWithError
        Push    "R1,R2,R14"
        BL      MangleCMOSAddress
        BCC     %FT10
        ADR     R0, ErrorBlock_CoreNotReadable
 [ International
        BL      TranslateError
 |
        SETV
 ]
        Pull    "R1,R2,PC"

        MakeErrorBlock CoreNotReadable

Read
        Push    "R1,R2,R14"
        BL      MangleCMOSAddress
        MOVCS   R0, #0                  ; pretend illegal addresses contain 0
        Pull    "R1,R2,PC", CS
10
        TEQ     R0, #&40                ; is it Econet station number
        BEQ     %FT15                   ; if so then don't use cache
        CMP     R0, #&10                ; don't cache the clock
  [     E2ROMSupport
        BHS     %FT13
        ; If our CMOS is actually inside an RTC, read direct (the cache is nonsense)
        LDR     R14, =ZeroPage
        LDRB    R14, [R14, #NVRamBase]
        TEQ     R14, #RTCAddressPHI
        BEQ     %FT15
  |
        BLO     %FT15
  ]
13      CMP     R0, #&100               ; check small cache limit
        LDRCC   R2, =ZeroPage+CMOSRAMCache ; if in range
        LDRCCB  R0, [R2, R0]            ; read from cache
        Pull    "R1,R2,PC", CC          ; and exit
15

; else drop thru into real CMOS reading code

  [ HAL
        Push    "R3,R4,sb,R12"
        MOV     R4, R0                  ; save address
        AddressHAL
        CallHAL HAL_NVMemoryType
        AND     R0, R0, #NVMemoryFlag_Provision
        TEQ     R0, #NVMemoryFlag_None

        ; If there's no NVmemory, pretend addresses contain 0
        Pull    "R3,R4,sb,R12", EQ
        MOVEQ   R0, #0
        Pull    "R1,R2,PC", EQ

        TEQ     R0, #NVMemoryFlag_HAL
        MOV     R0, R4                  ; restore address
        BNE     %FT20

        ; Make the HAL call - we have to provide a buffer.
        SUB     sp, sp, #4              ; make some space on the stack
        MOV     R1, sp
        MOV     R2, #1
        CallHAL HAL_NVMemoryRead
        LDRB    R0, [sp], #4            ; read back from stack and restore
        Pull    "R3,R4,sb,R12"
        Pull    "R1,R2,PC"

20
        Pull    "R3,R4, sb,R12"
  ]

  [     E2ROMSupport
        BL      GetI2CAddress           ; convert to device address and offset
  |
        MOV     R1, #RTCAddressPHI
  ]

        SUB     R13, R13, #2*12+4
        MOV     R14, R13
        TST     R1, #&100
        MOVNE   R2, R0, LSR #8
        STRNEB  R2, [R14], #1           ; offset (MSB)
        STRB    R0, [R14], #1           ; offset (LSB)
        SUB     R14, R14, R13
        STR     R13, [R13, #8]          ; transfer 1 data
        STR     R14, [R13, #12]         ; transfer 1 length
        AND     R14, R1, #&FF
        ORR     R2, R14, #1:SHL:29      ; retry
        STR     R2, [R13, #4]           ; transfer 1 address
        ORR     R14, R14, #1            ; device address for read
        STR     R14, [R13, #16]         ; transfer 2 address
        ADD     R14, R13, #3
        STR     R14, [R13, #20]         ; transfer 2 data
        MOV     R14, #1
        STR     R14, [R13, #24]         ; transfer 2 length
        ADD     R0, R13, #4
        MOV     R1, #2
        BL      IIC_OpV
        LDRB    R0, [R13, #3]
        ADD     R13, R13, #2*12+4

        Pull    "R1,R2,PC"

; *****************************************************************************
;
;       ReadBlock - Read a block of CMOS RAM specified by logical address
;
; in:   R0 = address in CMOS RAM
;       R1 = address to copy to
;       R2 = length
;
; out:  All registers preserved
;


ReadBlock ROUT
        Push    "R0-R3,R14"
  [     E2ROMSupport
        LDR     R14, =ZeroPage
        LDRB    R14, [R14, #NVRamSize]
        MOV     R14, R14, LSL #8
  |
        MOV     R14, #240
  ]

        CMP     R0, R14
        BHS     %FT90

        ADDS    R3, R0, R2              ; R3 = end address - check unsigned overflow
        BCS     %FT90
        CMP     R3, R14
        BHI     %FT90

        TEQ     R2, #0
        BEQ     %FT80

10      BL      ReadSubBlock
        BVS     %FT80
        TEQ     R2, #0
        BNE     %BT10
80
        Pull    "R0-R3,PC"

90
        ADD     SP, SP, #4              ; junk stacked R0
        ADR     R0, ErrorBlock_CoreNotReadable
 [ International
        BL      TranslateError
 |
        SETV
 ]
        Pull    "R1-R3,PC"

; *****************************************************************************
;
;       ReadSubBlock - Read a block of CMOS RAM specified by logical address.
;                      Assumes the address is valid, and will only read as much
;                      as it can in a single IIC transaction.
;
; in:   R0 = address in CMOS RAM
;       R1 = address to copy to
;       R2 = length
;
; out:  R0-R2 updated to reflect the amount read.
;
ReadSubBlock ROUT
        Push    "R3-R5,R14"
; establish end of the current contiguous block, and the logical->physical address offset.
        CMP     R0, #1                  ; 00 -> 40 uncached
        MOVLO   R3, #1
        MOVLO   R4, #&40-&00
        BLO     %FT10
        CMP     R0, #&C0                ; [01..C0) -> [41..100) cached
        MOVLO   R3, #&C0
        MOVLO   R4, #&41-&01
        BLO     %FT10
        CMP     R0, #&F0                ; [C0..F0) -> [10..40) cached
        MOVLO   R3, #&F0
        MOVLO   R4, #&10-&C0
        BLO     %FT10
        CMP     R0, #&100               ; [F0..100) -> [00..10) cached
        MOVLO   R3, #&100
        MOVLO   R4, #&00-&F0
        ADDHS   R3, R0, R2              ; [100..) -> [100..) uncached
        MOVHS   R4, #0
; R3 = logical end of current segment (exclusive)
; R4 = offset from logical to physical address for this segment
10
        ADD     R14, R0, R2
        CMP     R3, R14
        MOVHI   R3, R14
; R3 = logical end of this transaction (exclusive)
        TEQ     R0, #0                  ; check it's a cacheable segment
        BEQ     %FT15
        CMP     R0, #&100
        BHS     %FT15

        LDR     R14, =ZeroPage+CMOSRAMCache
        ADD     R3, R3, R4              ; R3 = physical end address
        ADD     R4, R4, R0              ; R4 = physical address
        ADD     R3, R3, R14             ; R3 = cache end address
        ADD     R4, R4, R14             ; R4 = cache address
        SUB     R14, R3, R4             ; R14 = bytes being read
        ADD     R0, R0, R14             ; update return R0
        SUB     R2, R2, R14             ; update return R2

12      LDRB    R14, [R4], #1
        CMP     R4, R3
        STRB    R14, [R1], #1
        BLO     %BT12
        Pull    "R3-R5,PC"              ; V will be clear
15
        Push    "R0-R2"
        ADD     R0, R0, R4              ; R0 = physical start address
        ADD     R3, R3, R4              ; R3 = physical end address
  [ HAL
        Push    "sb"
        SUB     R2, R3, R0
        MOV     R5, R0                  ; save address
        AddressHAL
        Push    "R1-R3,R12"
        CallHAL HAL_NVMemoryType
        Pull    "R1-R3,R12"
        AND     R0, R0, #NVMemoryFlag_Provision
        TEQ     R0, #NVMemoryFlag_None

        ; If there's no NVmemory, tough - we just return.
        MOVEQ   R2, #0                  ; nothing read
        Pull    "sb", EQ
        Pull    "R3-R5,PC", EQ

        TEQ     R0, #NVMemoryFlag_HAL
        MOV     R0, R5                  ; restore address
        BNE     %FT17                   ; do IIC things.

        ; Make the HAL call
        Push    "R12"
        CallHAL HAL_NVMemoryRead        ; returns bytes read in R0
        Pull    "R12"
        MOV     R4, R0
        Pull    "sb"
        Pull    "R0-R2"
        ADD     R0, R0, R4
        ADD     R1, R1, R4
        SUB     R2, R2, R4
        Pull    "R3-R5,PC"

17
        Pull    "sb"
  ]

        SUB     R5, R3, R0              ; R5 = bytes being read

  [     E2ROMSupport
        BL      GetI2CAddress           ; convert to device address and offset
  |
        MOV     R1, #RTCAddressPHI
  ]


        MOV     R2, R0                  ; save the offset
        SUB     R13, R13, #12*2+4
        MOV     R14, R13
        TST     R1, #&100               ; 2-byte address?
        MOVNE   R0, R2, LSR #8
        STRNEB  R0, [R14], #1           ; offset (MSB)
        STRB    R2, [R14], #1           ; offset (LSB)

        SUB     R14, R14, R13
        STR     R14, [R13, #12]         ; transfer 1 length

        AND     R14, R1, #&FF
        ORR     R0, R14, #1:SHL:29      ; retry
        STR     R0, [R13, #4]           ; transfer 1 address
        ORR     R14, R14, #1            ; device address for read
        STR     R14, [R13, #16]         ; transfer 2 address
        STR     R13, [R13, #8]          ; transfer 1 data

        ADD     R14, R13, #12*2+4
        LDMIA   R14, {R0-R2}
        ADD     R0, R0, R5              ; update return R0
        SUB     R2, R2, R5              ; update return R2
        STMIB   R14, {R0,R2}

        STR     R1, [R13, #20]          ; transfer 2 data
        STR     R5, [R13, #24]          ; transfer 2 length
        ADD     R0, R13, #4
        MOV     R1, #2
        BL      IIC_OpV

        LDR     R1, [R13, #20]          ; recover data pointer
        ADD     R1, R1, R5
        ADD     R13, R13, #12*2+4+4

        CLRV

        Pull    "R0,R2,R3-R5,PC"

; *****************************************************************************
;
;       ChecksumBlock - Checksum a block of CMOS RAM specified by logical address
;                       Assumes the address is valid.
;
; in:   R0 = address in CMOS RAM
;       R1 = initial checksum
;       R2 = length
;
; out:  R1 incremented by sum of bytes in range
;


ChecksumBlock ROUT
        Push    "R0,R2,R14"

10      BL      ChecksumSubBlock
        BVS     %FT80
        TEQ     R2, #0
        BNE     %BT10
80
        Pull    "R0,R2,PC"

; *****************************************************************************
;
;       ChecksumSubBlock - Checksum a block of CMOS RAM specified by logical address.
;                          Assumes the address is valid, and will only read as much
;                          as it can in a single IIC transaction. Skips over
;                          239 (the checksum byte itself), and 240-255 (OTP area).
;
; in:   R0 = address in CMOS RAM
;       R1 = initial checksum
;       R2 = length
;
; out:  R0-R2 updated to reflect the data read.
;
ChecksumSubBlock ROUT
        Push    "R3-R5,R14"
; establish end of the current contiguous block, and the logical->physical address offset.
        CMP     R0, #1                  ; 00 -> 40 uncached
        MOVLO   R3, #1
        MOVLO   R4, #&40-&00
        BLO     %FT10
        CMP     R0, #&C0                ; [01..C0) -> [41..100) cached
        MOVLO   R3, #&C0
        MOVLO   R4, #&41-&01
        BLO     %FT10
        CMP     R0, #&EF                ; [C0..EF) -> [10..3F) cached
        MOVLO   R3, #&EF
        MOVLO   R4, #&10-&C0
        BLO     %FT10
        CMP     R0, #&100
        ADDHS   R3, R0, R2              ; [100..) -> [100..) uncached
        MOVHS   R4, #0
        BHS     %FT10

;  [EF..100) -> not checksummed
        MOV     R3, #&100
        ADD     R14, R0, R2
        CMP     R3, R14
        MOVHI   R3, R14
        SUB     R14, R3, R0
        ADD     R0, R0, R14
        SUB     R2, R2, R14
        Pull    "R3-R5,PC"


; R3 = logical end of current segment (exclusive)
; R4 = offset from logical to physical address for this segment
10
        ADD     R14, R0, R2
        CMP     R3, R14
        MOVHI   R3, R14
; R3 = logical end of this transaction (exclusive)

        TEQ     R0, #0                  ; check it's a cacheable segment
        BEQ     %FT15
        CMP     R0, #&100
        BHS     %FT15

        LDR     R14, =ZeroPage+CMOSRAMCache
        ADD     R3, R3, R4              ; R3 = physical end address
        ADD     R4, R4, R0              ; R4 = physical address
        ADD     R3, R3, R14             ; R3 = cache end address
        ADD     R4, R4, R14             ; R4 = cache address
        SUB     R14, R3, R4             ; R14 = bytes being read
        ADD     R0, R0, R14             ; update return R0
        SUB     R2, R2, R14             ; update return R2

12      LDRB    R14, [R4], #1
        CMP     R4, R3
        ADD     R1, R1, R14
        BLO     %BT12
        Pull    "R3-R5,PC"
15
        Push    "R0-R2"
        ADD     R0, R0, R4              ; R0 = physical start address
        ADD     R3, R3, R4              ; R3 = physical end address
  [ HAL
        Push    "sb,R12"
        MOV     R5, R0                  ; save address
        AddressHAL
        Push    "R1-R3"
        CallHAL HAL_NVMemoryType
        Pull    "R1-R3"
        AND     R4, R0, #NVMemoryFlag_Provision
        MOV     R0, R5                  ; restore address
        TEQ     R4, #NVMemoryFlag_None
        TEQNE   R4, #NVMemoryFlag_HAL
        BNE     %FT17                   ; do IIC things.

        SUB     R14, R3, R0
        LDR     R1, [R13,#8]
        LDR     R3, [R13,#16]
        ADD     R1, R1, R14
        SUB     R3, R3, R14
        STR     R1, [R13,#8]
        STR     R3, [R13,#16]

        TEQ     R4, #NVMemoryFlag_None
        ; If there's no NVmemory, tough - we just return.
        Pull    "sb,R12", EQ
        Pull    "R0-R5,PC", EQ

        Push    "R6"
        MOV     R4, #0
        ADD     R6, R5, R14
        SUB     R13, R13, #4
16
        MOV     R0, R5
        MOV     R1, sp
        MOV     R2, #1
        CallHAL HAL_NVMemoryRead
        LDRB    R14, [R13]
        ADD     R4, R4, R14
        ADD     R5, R5, #1
        TEQ     R5, R6
        BNE     %BT16
        ADD     R13, R13, #4
        Pull    "R6,sb,R12"
        Pull    "R0-R2"
        ADD     R1,R1,R4
        Pull    "R3-R5,PC"
17
        Pull    "sb,R12"
  ]
        SUB     R5, R3, R0              ; R5 = bytes being read

  [     E2ROMSupport
        BL      GetI2CAddress           ; convert to device address and offset
  |
        MOV     R1, #RTCAddressPHI
  ]

        MOV     R2, R0                  ; save the offset
        SUB     R13, R13, #12*2+4
        MOV     R14, R13
        TST     R1, #&100               ; 2-byte address?
        MOVNE   R0, R2, LSR #8
        STRNEB  R0, [R14], #1           ; offset (MSB)
        STRB    R2, [R14], #1           ; offset (LSB)

        SUB     R14, R14, R13
        STR     R14, [R13, #12]         ; transfer 1 length

        AND     R14, R1, #&FF
        ORR     R0, R14, #1:SHL:29      ; retry
        STR     R0, [R13, #4]           ; transfer 1 address
        ORR     R14, R14, #1            ; device address for read
        ORR     R14, R14, #1:SHL:30     ; checksum only please
        STR     R14, [R13, #16]         ; transfer 2 address
        STR     R13, [R13, #8]          ; transfer 1 data

        ADD     R14, R13, #12*2+4
        LDMIA   R14, {R0-R2}
        ADD     R0, R0, R5              ; update return R0
        SUB     R2, R2, R5              ; update return R2
        STMIB   R14, {R0,R2}
        MOV     R4, R1                  ; remember checksum

        STR     R5, [R13, #24]          ; transfer 2 length
        ADD     R0, R13, #4
        MOV     R1, #2
        BL      IIC_OpV

        LDR     R1, [R13, #20]          ; read back checksum
        ADD     R1, R1, R4              ; update checksum
        ADD     R13, R13, #12*2+4+4

        Pull    "R0,R2,R3-R5,PC"

; *****************************************************************************
;
;       GetI2CAddress - Convert NVRam physical address to i2c device address
;                       and offset
;
; in:   R0 = NVRam physical address (&00..size of NVRam)
;
; out:  R0 preserved
;
;       C=0 => NVRam address is valid
;        R0 = physical address within i2c device
;        R1 = i2c device address for writing. Increment this device address
;             by 1 for reading. Bit 8 is set if device requires 2-byte physical address.
;
;       C=1 => NVRam address is out of range of CMOS or E2ROM chips
;        R0 preserved
;        R1 preserved

  [     E2ROMSupport
GetI2CAddress ROUT
        Push    "R14"
        LDR     R14, =ZeroPage          ; get no 256 byte blocks and calculate end address
        LDRB    R14, [R14, #NVRamSize]
        MOV     R14, R14, LSL #8
        CMP     R0, R14
        Pull    "PC",CS                 ; indicate invalid

; address is < end address -> is valid
        LDR     R1, =ZeroPage
        LDRB    R1, [R1, #NVRamBase]

        CMP     R14, #2*1024            ; is the device bigger than 2K? If so, new addressing scheme
        ORRHI   R1, R1, #&100           ; set magic bit => 2 byte address
        BHI     %FT50

        MOVS    R14, R0, LSR #8         ; put top bits of physical address into device address
        ORRNE   R1, R1, R14, LSL #1
        ANDNE   R0, R0, #&FF            ; and use address within 256 byte block
50
        CLC
        Pull    "PC"                    ; indicate valid

  ]

; *****************************************************************************
;
;       MangleCMOSAddress - Convert from logical to physical address
;
;       Doesn't check if address is larger than the amount of NVRam installed
;
; in:   R0 = logical address (&00...)
;
; out:  C=0 => valid logical address
;        R0 = physical address (&40..&FF,&10..&3F,&00..0F,&100..)
;
;       C=1 => invalid logical address
;        R0 preserved
;

MangleCMOSAddress ROUT
 [ E2ROMSupport
        Push    "R14"
        LDR     R14, =ZeroPage          ; read no 256 byte blocks and calculate end address
        LDRB    R14, [R14, #NVRamSize]
        MOV     R14, R14, LSL #8
        CMP     R0, R14                 ; if >= end address then
        Pull    "R14"
        MOVCS   PC, R14                 ;    invalid (exit C set)

        CMP     R0, #&100               ; if < end address && >= &100 then
        BLO     %FT05
        CLC
        MOV     PC, R14                 ;    valid (no mungeing)
 ]
05
        CMP     R0, #&F0                ; if < &100 && >= &f0 then
        [ E2ROMSupport
        BCC     %FT10
        SUB     R0, R0, #&F0            ;    map &F0->&FF to &00->0F for OTP section
        CLC
        MOV     PC, R14
        |
        MOVCS   PC, R14                 ;    invalid
        ]
10
        ADD     R0, R0, #&40            ; now in range &40..&13F
        CMP     R0, #&100
        SUBCS   R0, R0, #(&100-&10)     ; now in range &40..&FF, &10..&3F
        CLC
        MOV     PC, R14                 ; valid

; *****************************************************************************
;
;       ValChecksum - test to see if the CMOS checksum is OK
;
;       This routine performs MangleCMOSAddress inherently.
;
;       The checksum does not include physical locations &00->&0F, even
;       if they are OTP section (as this is usually used for a unique id
;       which will be different for every machine and can't be changed).
;
; in:   none
;
; out:  R0 = calculated checksum
;       Z       set if checksum is valid
;       All other registers preserved
;

  [ ChecksumCMOS

ValChecksum     Entry "R1-R2"

        MOV     R0, #0
        MOV     R1, #CMOSxseed
   [ E2ROMSupport
        LDR     R2, =ZeroPage           ; read number of 256 byte blocks and calculate end address
        LDRB    R2, [R2, #NVRamSize]
        MOV     R2, R2, LSL #8
   |
        MOV     R2, #240
   ]
        BL      ChecksumBlock

;
; R1 contains the actual checksum. Compare it with the recorded checksum
;
40
        MOV     R0, #CheckSumCMOS
        BL      Read
        AND     R2, R0, #&FF            ; value from checksum location
        AND     R0, R1, #&FF            ; calculated value into R0
        CMPS    R0, R2

        EXIT
  ]

; *****************************************************************************
;
;       MakeChecksum - calculate and write a correct checksum
;
; in:   none
;
; out:  R0 = calculated checksum
;       All other registers preserved
;

        [ ChecksumCMOS

MakeChecksum    ROUT
        Push    "R1-R2,R14"
        MOV     R0, #0
        MOV     R1, #CMOSxseed
  [ E2ROMSupport
        LDR     R2, =ZeroPage
        LDRB    R2, [R2, #NVRamSize]
        MOV     R2, R2, LSL #8
  |
        MOV     R2, #240
  ]
        BL      ChecksumBlock
        MOV     R0, #CheckSumCMOS
        BL      Write
        Pull    "R1-R2,PC"
        ]

        LTORG

; *****************************************************************************
;
;       InitCMOSCache - Initialise cache of CMOS RAM
;  in: -
;
;  out: R0 = 0 for failure

InitCMOSCache   Entry "r1-r6, sb,r12"
    [   E2ROMSupport

        ; Need to set the slowest speed so we can probe
        LDR     R4, =ZeroPage
        MOV     R3, #10         ; Default speed setting (5�s delays)
        STRB    R3, [R4, #NVRamSpeed]

 [ HAL
        AddressHAL
        CallHAL HAL_NVMemoryType
        MOV     R5, R0
        ANDS    R0, R0, #NVMemoryFlag_Provision
        ASSERT  NVMemoryFlag_None = 0
        BEQ     InitCMOSCache_NoCMOS

        ; If it's only a maybe, then we probe
        TEQ     R0, #NVMemoryFlag_MaybeIIC
        BEQ     %FT03

        ; Else we read the size

        CallHAL HAL_NVMemorySize        ; returns number of bytes but..
        MOV     R0, R0, LSR#8           ; .. expecting no. of 256 blocks
        STRB    R0, [R4, #NVRamSize]

        TST     R5, #NVMemoryFlag_ProtectAtEnd
        STREQB  R0, [R4, #NVRamWriteSize]
        BEQ     %FT02
        CallHAL HAL_NVMemoryProtectedSize
        LDRB    R1, [R4, #NVRamSize]
        SUB     R0, R1, R0, LSR#8
        STRB    R0, [R4, #NVRamWriteSize]

02
        CallHAL HAL_NVMemoryPageSize    ; returns size in bytes but..
        TEQ     R0, #0                  ; .. expecting power of 2
        MVNEQ   R0, #0
        MOV     R1, #0
22      MOVS    R0, R0, LSR #1
        ADDNE   R1, R1, #1
        BNE     %BT22
        STRB    R1, [R4, #NVRamPageSize]

        CallHAL HAL_NVMemoryIICAddress
        STRB    R0, [R4, #NVRamBase]

        MOV     R0, #0
        CallHAL HAL_IICType
        MOV     R3, #10
        TST     R0, #IICFlag_Fast
        MOVNE   R3, #3
        TST     R0, #IICFlag_HighSpeed
        MOVNE   R3, #1
        STRB    R3, [R4, #NVRamSpeed]

        ; If we're using IIC then read in the cache manually
        AND     R0, R5, #NVMemoryFlag_Provision
        TEQ     R0, #NVMemoryFlag_IIC
        BEQ     %FT06

        ; Else use HAL routine.
        MOV     R0, #0
        LDR     R1, =ZeroPage+CMOSRAMCache
        MOV     R2, #&100
        CallHAL HAL_NVMemoryRead
        TEQ     R0, #&100
        MOVNE   R0, #0                  ; Failure exit condition
        EXIT
03
 ]

;       No HAL,so determine what hardware we've got fitted by probing,
;       R4 holds the number of 256 byte blocks that we've found

        MOV     R3, #10         ; assume 100kHz to start with
        MOV     R5, #4          ; assume 16 byte page size to start with
        MOV     R6, #0          ; assume not protected

; Have we got a 2K device ?
        MOV     r1, #E2ROMAddress2K
        MOV     r0, #(E2ROMAddress2K+14)
        BL      DummyAccess
        MOVVC   R4, #8
        MOVVC   R3, #3          ; Fast speed setting (1.5�s delays)
        BVC     %FT5

; Have we got a 16K device ?
        MOV     r1, #E2ROMAddress16K
        MOV     r0, #E2ROMAddress16K
        BL      DummyAccess
        MOVVC   R4, #64
        MOVVC   R5, #6          ; 64 byte page size
        MOVVC   R3, #3          ; Fast speed setting (1.5�s delays)
        BVC     %FT5

; Have we got a 4K device?
        MOV     r1, #E2ROMAddress4K
        MOV     r0, #E2ROMAddress4K
        BL      DummyAccess
        MOVVC   R4, #16
        MOVVC   R6, #12         ; Only bottom 3K writable
        MOVVC   R5, #5          ; 32 byte page size
        MOVVC   R3, #3          ; Fast speed setting (1.5�s delays)
        BVC     %FT5

; Have we got an 8K device?
        MOV     r1, #E2ROMAddress8K
        MOV     r0, #E2ROMAddress8K
        BL      DummyAccess
        MOVVC   R4, #32
        MOVVC   R5, #5          ; 32 byte page size
        MOVVC   R3, #3          ; Fast speed setting (1.5�s delays)
        BVC     %FT5

; Have we got a protected 8K device?
        MOV     r1, #E2ROMAddress8K_prot
        MOV     r0, #E2ROMAddress8K_prot
        BL      DummyAccess
        MOVVC   R4, #32
        MOVVC   R6, #24         ; Only bottom 6K writable
        MOVVC   R5, #5          ; 32 byte page size
        MOVVC   R3, #3          ; Fast speed setting (1.5�s delays)
        BVC     %FT5

; Have we got a 32K device?
        MOV     r1, #E2ROMAddress32K
        MOV     r0, #E2ROMAddress32K
        BL      DummyAccess
        MOVVC   R4, #128        ; 128,120,6,1
        MOVVC   R6, #120        ; Only bottom 30K writable
        MOVVC   R5, #6          ; 64 byte page size
        MOVVC   R3, #1          ; Hyper-fast speed setting (0.5�s delays - 1MHz part)
        BVC     %FT5

; Any storage in the Philips RTC?
        MOV     R1, #RTCAddressPHI
        MOV     R0, #RTCAddressPHI
        BL      DummyAccess
        MOV     R5, #8          ; 256 byte page size for CMOS
        MOVVC   R4, #1
        BVC     %FT5

; We ain't got anything!
InitCMOSCache_NoCMOS
        LDR     R2, =ZeroPage
        MOV     R5, #8
        STRB    R5, [R2, #NVRamPageSize]        ; Act as though we have 256 bytes of
        MOV     R1, #1                          ; single page CMOS.
        STRB    R1, [R2, #NVRamSize]
        STRB    R1, [R2, #NVRamWriteSize]
        MOV     R0, #0          ; Exit failure
        EXIT

5
        ; Set the NVRam count
        LDR     R2, =ZeroPage
        STRB    R1, [R2, #NVRamBase]
        STRB    R4, [R2, #NVRamSize]
        STRB    R5, [R2, #NVRamPageSize]
        TEQ     R6, #0
        MOVEQ   R6, R4
        STRB    R6, [R2, #NVRamWriteSize]

        CMP     R3, #I2Cticks   ; clamp speed to maximum bus speed
        MOVLO   R3, #I2Cticks
        STRB    R3, [R2, #NVRamSpeed]
06
        ; Initialise the cache
        LDR     R3, =ZeroPage+CMOSRAMCache

        TEQ     R4, #8                  ; check for 2K part
        MOVNE   r0, #&00                ; if not, then start at 0 anyway and read non-OTP data into location 0..15
        BNE     %FT07
        BL      ReadOTPArea
        MOV     r0, #&10                ; read rest of it from 16 onwards
07
        BL      GetI2CAddress           ; and convert to device address and offset
        MOV     R2, R0                  ; save the offset
        MOV     R4, #&100               ; stop at &100
    |
        ; No E2ROM support, assume just a Philips RTC
        MOV     R1, #RTCAddressPHI
        MOV     R2, #&10
        MOV     R4, #&100               ; stop at address &100
        LDR     R3, =ZeroPage+CMOSRAMCache
    ]

        ; Note - R4 MUST be &100 to prevent crossover between 256-byte pages
        ; (for devices with multiple addresses)
09

        SUB     R13, R13, #2*12+4
        AND     R0, R1, #&FF
        STR     R0, [R13, #4]           ; transfer 1 address
        ADD     R0, R0, #1              ; read address
        STR     R0, [R13, #16]          ; transfer 2 address
        TST     R1, #&100               ; 2-byte address?
        MOV     R14, R13
        MOVNE   R0, R2, LSR #8
        STRNEB  R0, [R14], #1           ; memory word address (MSB)
        STRB    R2, [R14], #1           ; memory word address (LSB)
        STR     R13, [R13, #8]          ; transfer 1 data
        SUB     R14, R14, R13
        STR     R14, [R13, #12]         ; transfer 1 length
        ADD     R14, R3, R2
        STR     R14, [R13, #20]         ; transfer 2 data
        SUB     R14, R4, R2
        STR     R14, [R13, #24]         ; transfer 2 length

        ADD     R0, R13, #4
        MOV     R1, #2
        BL      IIC_OpV

        ADD     R13, R13, #2*12+4
        MOV     R0, #1
        EXIT

   [ E2ROMSupport
ReadOTPArea Entry
        SUB     R13, R13, #2*12+4
        MOV     R0, #0
        STRB    R0, [R13, #0]           ; offset 0
        MOV     R0, #E2ROMAddress2K_OTP
        STR     R0, [R13, #4]           ; transfer 1 address
        STR     R13, [R13, #8]          ; transfer 1 data
        MOV     R0, #1
        STR     R0, [R13, #12]          ; transfer 1 length
        MOV     R0, #E2ROMAddress2K_OTP + 1
        STR     R0, [R13, #16]          ; transfer 2 address
        STR     R3, [R13, #20]          ; transfer 2 data
        MOV     R0, #16
        STR     R0, [R13, #24]          ; transfer 2 length
        ADD     R0, R13, #4
        MOV     R1, #2
        BL      IIC_OpV
        ADD     R13, R13, #2*12+4
        EXIT
   ]

; *****************************************************************************
;
;       DummyAccess - do a dummy access of the specified device to find out
;                     if it is present
;
; in:   R0 = Write address of device
;
; out:  All registers preserved
;       V=0 => device is present
;       V=1 => device is not present

  [ E2ROMSupport
DummyAccess

 [ {TRUE}
        ; Blooming 80321 HW IIC can't do just START address STOP
        Entry   "R0-R2",4
        ORR     R0, R0, #1
        MOV     R1, R13
        MOV     R2, #1
        BL      IIC_Op
 |
        Entry   "R1,R2"
        MOV     R1, #0
        MOV     R2, #0
        BL      IIC_Op
 ]

        EXIT                            ; Exit with V set appropriately
  ]

; *****************************************************************************
;
;       SWI OS_NVMemory
;
; in:   R0 = reason code
;

NVMemorySWI     Entry
        BL      NVMemorySub
        PullEnv
        ORRVS   LR, LR, #V_bit
        ExitSWIHandler

NVMemorySub
        CMP     R0, #7
        ADDLS   PC, PC, R0, LSL #2
        B       NVMemory_Unknown
        B       NVMemory_Size
        B       NVMemory_Read
        B       NVMemory_Write
        B       NVMemory_ReadBlock
        B       NVMemory_WriteBlock
        B       NVMemory_Unknown        ; Reserved for Kernel-5_41 divergence
        B       NVMemory_ResetValue
        B       NVMemory_SetStation

NVMemory_Unknown
        ADRL    R0, ErrorBlock_HeapBadReason
      [ International
        Push    LR
        BL      TranslateError
        Pull    LR
      ]
        RETURNVS

; -----------------------------------------------------------------------------
; OS_NVMemory 0 - find NV memory size
;
; in:   R0 = 0
;
; out:  R1 = NV memory size in bytes
;
NVMemory_Size
    [ E2ROMSupport
      [ ZeroPage = 0
        LDRB    R1, [R0, #NVRamSize]
      |
        LDR     R1, =ZeroPage
        LDRB    R1, [R1, #NVRamSize]
      ]
        MOV     R1, R1, LSL #8
    |
        MOV     R1, #240
    ]
        MOV     PC, LR

; -----------------------------------------------------------------------------
; OS_NVMemory 1 - read a byte
;
; in:   R0 = 1
;       R1 = location
;
; out:  R2 = value
;
NVMemory_Read
        Entry   "R4"
        MRS     R4, CPSR
        BIC     R0, R4, #I32_bit
        MSR     CPSR_c, R0      ; enable interrupts - this may take some time
        MOV     R0, R1
        BL      ReadWithError
        MOVVC   R2, R0
        MOVVC   R0, #1          ; must preserve R0
        ORRVS   R4, R4, #V_bit
        MSR     CPSR_cf, R4     ; restore interrupt state
        EXIT

; -----------------------------------------------------------------------------
; OS_NVMemory 2 - write a byte
;
; in:   R0 = 2
;       R1 = location
;       R2 = value
;
NVMemory_Write ROUT
      [ ProtectStationID
        TEQ     R1, #NetStnCMOS ; just ignore writes
        MOVEQ   PC, R14
      ]
        Entry   "R1,R4"
        MRS     R4, CPSR
        BIC     R0, R4, #I32_bit
        MSR     CPSR_c, R0      ; enable interrupts - this may take some time
        MOV     R0, R1
        MOV     R1, R2
        BL      WriteWithError
        MOVVC   R0, #2          ; must preserve R0
        ORRVS   R4, R4, #V_bit
        MSR     CPSR_cf, R4     ; restore interrupt state
        EXIT

; -----------------------------------------------------------------------------
; OS_NVMemory 3 - read a block
;
; in:   R0 = 3
;       R1 = location
;       R2 = buffer
;       R3 = length
;
NVMemory_ReadBlock
        Entry   "R1-R4"
        MRS     R4, CPSR
        BIC     R0, R4, #I32_bit
        MSR     CPSR_c, R0      ; enable interrupts - this may take some time
        MOV     R0, R1
        MOV     R1, R2
        MOV     R2, R3
        BL      ReadBlock
        MOVVC   R0, #3          ; must preserve R0
        ORRVS   R4, R4, #V_bit
        MSR     CPSR_cf, R4     ; restore interrupt state
        EXIT

; -----------------------------------------------------------------------------
; OS_NVMemory 4 - write a block
;
; in:   R0 = 4
;       R1 = location
;       R2 = buffer
;       R3 = length
;
NVMemory_WriteBlock ROUT
        Entry   "R1-R4"
        MRS     R4, CPSR
        BIC     R0, R4, #I32_bit
        MSR     CPSR_c, R0      ; enable interrupts - this may take some time
      [ ProtectStationID
        ASSERT  NetStnCMOS = 0
        TEQ     R1, #NetStnCMOS
        BNE     %FT10
        ADD     R1, R1, #1
        ADD     R2, R2, #1
        TEQ     R3, #0
        SUBNE   R3, R3, #1      ; steer clear of station ID
      ]
10      MOV     R0, R1
        MOV     R1, R2
        MOV     R2, R3
        BL      WriteBlock
        MOVVC   R0, #4          ; must preserve R0
        ORRVS   R4, R4, #V_bit
        MSR     CPSR_cf, R4     ; restore interrupt state
        EXIT

; -----------------------------------------------------------------------------
; OS_NVMemory 6 - query CMOS value that would be used on a Delete-Power-On reset
;
; in:   R0 = 6
;       R1 = location
;
; out:  R2 = value, or -1 if the value is unknown/untouched
;
NVMemory_ResetValue ROUT
        Entry   "R3"
        MOV     R2, #-1         ; assume outside our remit
        TEQ     R1, #NetStnCMOS
        EXIT    EQ
        ASSERT  CheckSumCMOS = CMOSLimit - 1
        CMP     R1, #CheckSumCMOS
        EXIT    CS

        TEQ     R1, #MouseCMOS
        BEQ     %FT20           ; code driven

        TEQ     R1, #PrintSoundCMOS
        BEQ     %FT30           ; code driven

    [ STB :LAND: :DEF: IOMD_C_PALNTSCType
        TEQ     R1, #TerritoryCMOS
        BEQ     %FT40           ; code driven
        TEQ     R1, #CountryCMOS
        BEQ     %FT41           ; code driven
        TEQ     R1, #TimeZoneCMOS
        BEQ     %FT42           ; code driven
    ]
        ADR     R3, DefaultCMOSTable
10
        LDRB    R2, [R3], #2    ; table is of location/value pairs...
        TEQ     R2, #&FF        ; ...terminated by &FF
        MOVEQ   R2, #0          ; not in the table, so must be zero
        EXIT    EQ
        TEQ     R2, R1          ; location match?
        LDREQB  R2, [R3, #-1]
        EXIT    EQ
        B       %BT10
20
    [ HAL
      [ "$Machine"="IOMD"
        Push    "R0-R1,R9,R12"
        AddressHAL
        MOV     R0, #0
        MOV     R1, #&400                    ; SSpace hopefully exists
        CallHAL HAL_ControllerAddress
        LDRB    R1, [R0, #IOMD_ID1]
        LDRB    R0, [R0, #IOMD_ID0]
        ORR     R0, R0, R1, LSL #8
        LDR     R1, =IOMD_Original
        TEQ     R0, R1                       ; Select quadrature or PS2 as appropriate
        MOVEQ   R2, #PointerDevice_QuadMouse ; Risc PC
        MOVNE   R2, #PointerDevice_PS2Mouse  ; A7000 et al
        Pull    "R0-R1,R9,R12"
      |
        ; Everyone else is on USB
        MOV     R2, #PointerDevice_USB
      ]
    |
        Push    "R0-R1"
        MOV     R3, #IOMD_Base
        LDRB    R1, [R3, #IOMD_ID1]
        LDRB    R0, [R3, #IOMD_ID0]
        ORR     R0, R0, R1, LSL #8
        LDR     R1, =IOMD_Original
        TEQ     R0, R1
        MOVEQ   R2, #PointerDevice_QuadMouse ; Risc PC
        MOVNE   R2, #PointerDevice_PS2Mouse  ; A7000 et al
        Pull    "R0-R1"
    ]
        EXIT
30
    [ HAL
        MOV     R2, #2_10100100
                     ; ^^^       interpolate at low rates, 16 bit DAC, fully programmable rates
                     ;    ^^^^^  tbs chars valid, escape with GSTrans
    |
        Push    "R0-R1"
        MOV     R3, #IOMD_Base
        LDRB    R1, [R3, #IOMD_ID1]
        LDRB    R0, [R3, #IOMD_ID0]
        ORR     R0, R0, R1, LSL #8
        LDR     R1, =IOMD_7500
        TEQ     R0, R1
        LDRNE   R1, =IOMD_7500FE
        TEQNE   R0, R1
      [ STB
        MOVEQ   R2, #2_00000100              ; Cheapskates
      |
        MOVEQ   R2, #2_10100100              ; A7000 et al always have 16 bit sound
      ]
        BEQ     %FT31
        ; on Issue A's the protection bit is only weakly pulled up,
        ; so force it high, then read it back
        LDR     R3, =IOMD_MonitorType
        LDR     R1, [R3]
        ORR     R1, R1, #IOMD_SoundsystemLinkBit
        STR     R1, [R3]
        LDR     R1, [R3]
        TST     R1, #IOMD_SoundsystemLinkBit
        MOVEQ   R2, #2_10100100              ; if zero, must be Rimmer, so assume 16bit sound hardware present
        MOVNE   R2, #2_00000100              ; 8 bit sound on the motherboard (can't detect plugin upgrades)
31
        Pull    "R0-R1"
    ]
        EXIT
    [ STB :LAND: :DEF: IOMD_C_PALNTSCType
40
        MOV     R2, #0                       ; PAL = territory UK
        MOV     R3, #49                      ; NTSC = territory USA
        B       %FT44
41
        MOV     R2, #1                       ; PAL = country UK
        MOV     R3, #48                      ; NTSC = country USA
        B       %FT44
42
        MOV     R2, #0                       ; PAL = 0 from UTC (GMT)
        MOV     R3, #&E0                     ; NTSC = -8 hours from UTC (USA Pacific)
        B       %FT44
44
        MOV     R14, #IOMD_Base
        LDRB    R14, [R14, #IOMD_CLINES]
        TST     R14, #IOMD_C_PALNTSCType
        MOVNE   R2, R3                       ; Select NTSC when line high
        EXIT
    ]

; -----------------------------------------------------------------------------
; OS_NVMemory 7 - set station
;
; in:   R0 = 7
;       R1 = pointer to pass phrase
;       R2 = value
;
NVMemory_SetStation ROUT
        Entry   "R1-R4"
        MRS     R4, CPSR
        BIC     R0, R4, #I32_bit
        MSR     CPSR_c, R0      ; enable interrupts - this may take some time
      [ ProtectStationID
        MOV     R2, R1
10
        LDRB    R0, [R2], #1
        CMP     R0, #' '
        BCS     %BT10
        MOV     R3, #1
        SUB     R2, R2, #1
        MOV     R0, #0
        SWI     XOS_CRC
        EOR     R0, R0, #&009D
        EORS    R0, R0, #&2300
        MOVNE   R0, #-1         ; duff address to cause 'not writable' error
        ASSERT  NetStnCMOS = 0
        LDR     R1, [SP, #Proc_RegOffset + 4]
      |
        MOV     R0, #NetStnCMOS
        MOV     R1, R2
      ]
        BL      WriteWithError
        MOVVC   R0, #7          ; must preserve R0
        ORRVS   R4, R4, #V_bit
        MSR     CPSR_cf, R4     ; restore interrupt state
        EXIT

 [ ValidateCMOS :LAND: STB
DefaultCMOSTable
        ; Minimalist table
        DCB     KeyDelCMOS,         32
        DCB     KeyRepCMOS,         8
        DCB     MODETVCMOS,         &10                                     ; TV 0,1
        DCB     StartCMOS,          (1:SHL:7):OR:(2:SHL:3)                  ; NONUM, NOCAPS
        DCB     DBTBCMOS,           (1:SHL:4)                               ; Boot
        DCB     YearCMOS+0,         00
        DCB     YearCMOS+1,         20
      [ IOMD_C_MonitorType = 0 :LAND: IOMD_C_PALNTSCType = 0
        ; TV if we don't have a MonitorType auto-detect bit
        DCB     VduCMOS,            Sync_Separate :OR: MonitorType0
      |
        ; auto-detect if we have a MonitorType auto-detect bit
        DCB     VduCMOS,            Sync_Auto :OR: MonitorTypeAuto
      ]
        DCB     CountryCMOS,        1                                       ; UK
        DCB     MouseStepCMOS,      2
        DCB     SystemSpeedCMOS,    (1:SHL:2):OR:(1:SHL:4):OR:(0:SHL:5)
                                    ; Delete-etc reset
                                    ;              WimpMode auto
                                    ;                           Cache on
 |
DefaultCMOSTable
        ; Normal table
        DCB     KeyDelCMOS,         32
    [ "$Machine"="CortexA8" :LOR: "$Machine"="CortexA9"
        DCB     FileLangCMOS,       fsnumber_SCSIFS ; SCSIFS for OMAP3, etc.
    |
      [ "$Machine"="ARM11ZF" :LOR: "$Machine"="RPi"
        DCB     FileLangCMOS,       fsnumber_SDFS   ; SDFS for Pi, etc.
      |
        DCB     FileLangCMOS,       fsnumber_adfs   ; ADFS
      ]
    ]
        DCB     FontCMOS,           64      ; KJB 13-Dec-02: Changed to 256K from 64K
        DCB     PigCMOS,            10
        DCB     KeyRepCMOS,         8
        DCB     RMASizeCMOS,        0
        DCB     SpriteSizeCMOS,     0
        DCB     SysHeapCMOS,        8
        DCB     MODETVCMOS,         &10     ; TV 0,1
        DCB     NetFSIDCMOS,        254
        DCB     NetPSIDCMOS,        235
        DCB     PSITCMOS,           (3:SHL:2) :OR: (1:SHL:5)
                                    ; Baud 3
                                    ;                Print 1

        DCB     DBTBCMOS,           (1:SHL:4) :OR: (4:SHL:5)
                                    ; Boot (changed from NoBoot 01-Sep-93)
                                    ;                Data 4

        DCB     StartCMOS,          (4:SHL:0) :OR: (2:SHL:3) :OR: (1:SHL:6) :OR: (0:SHL:7)
                                    ; ^              ^              ^              ^
                                    ; ADFS DR.4      NOCAPS         NODIR (moot)   NUM
      [ :LNOT: STB
        DCB     NewADFSCMOS+0,      &41     ; floppies=1, ST506=0, IDE=1 (changed 01-Sep-93)
      ]
        DCB     NewADFSCMOS+1,      4_3333  ; step 3 for each drive
        DCB     NewADFSCMOS+2,      1       ; ADFSBuffers 1

        DCB     SoundCMOS,          &F0     ; speaker on, volume 7, channel 1

        DCB     LanguageCMOS,       11      ; typically module number of 'Desktop'
        DCB     YearCMOS+0,         00
        DCB     YearCMOS+1,         20
        DCB     NetFilerCMOS,       (0:SHL:0) :OR: (1:SHL:1) :OR: (0:SHL:2)
                                    ; FS list order by name
                                    ;                Use $.Arthurlib
                                    ;                               Large icons

        DCB     DesktopCMOS,        2_01000000      ; verbose ON
        DCB     WimpFlagsCMOS,      2_01101111      ; instant effects, drags off screen
        DCB     ProtectionCMOS,     2_01110110      ; allow only peek and user RPC
        DCB     MouseStepCMOS,      2
        DCB     FileSwitchCMOS,     (1:SHL:0) :OR: (1:SHL:1) :OR: (0:SHL:2) :OR: (0:SHL:3) :OR: (0:SHL:6)
                                    ; Truncate names
                                    ;                Use DragASprite (changed 01-Sept-93)
                                    ;                               Interactive file copying
                                    ;                                              Wimp dither colours off
                                    ;                                                             Last shutdown ordinary

        DCB     DesktopFeaturesCMOS,(1:SHL:0) :OR: (8:SHL:1) :OR: (0:SHL:7)
                                    ; 3D look
                                    ;                Homerton.Medium
                                    ;                               Tiled window background

      [ STB
        DCB     SystemSpeedCMOS,    (1:SHL:0):OR:(0:SHL:1):OR:(1:SHL:2):OR:(0:SHL:3):OR:(1:SHL:4):OR:(0:SHL:5):OR:(1:SHL:6):OR:(0:SHL:7)
                                    ; AUN ROMBoot Enabled
                                    ;              AUN auto-station numbering off
                                    ;                           Delete-etc reset
                                    ;                                        Power saving off
                                    ;                                                     WimpMode auto
                                    ;                                                                  Cache on
                                    ;                                                                               Broadcast loader disabled
                                    ;                                                                                            Broadcast loader colours off
      |
        DCB     SystemSpeedCMOS,    (0:SHL:0):OR:(0:SHL:1):OR:(1:SHL:2):OR:(0:SHL:3):OR:(1:SHL:4):OR:(0:SHL:5):OR:(1:SHL:6):OR:(0:SHL:7)
                                    ; AUN BootNet Disabled
                                    ;              AUN auto-station numbering off
                                    ;                           Delete-etc reset
                                    ;                                        Power saving off
                                    ;                                                     WimpMode auto
                                    ;                                                                  Cache on
                                    ;                                                                               Broadcast loader disabled
                                    ;                                                                                            Broadcast loader colours off
      ]

      [ STB
        ;       FontMaxCMOS                     yes, omitting is deliberate!
        DCB     FontMax2CMOS,       &2C       ; 32 point
        DCB     FontMax3CMOS,       &38       ; 32 point
      |
        DCB     FontMaxCMOS,        64        ; 4096k
        DCB     FontMax2CMOS,       36:EOR:12 ; 36 point
        DCB     FontMax3CMOS,       36:EOR:24 ; 36 point
        DCB     FontMax4CMOS,       16        ; 16 point
      ]
        DCB     AlarmAndTimeCMOS,   2_00010000           ; !Alarm autosave on
        DCB     FSLockCMOS+5,       &EA                  ; Checksum for no password
        DCB     SparrowMarker,      FreewayNoAutoAddress ; Stop Freeway assigning addresses to interfaces
        DCB     NetworkFlags,       LanManFStransport    ; LMTransport is NetBIOS over IP
        DCB     WimpDragMoveLimitCMOS, (1:SHL:7)         ; WimpIconiseButton
      [ "$Machine"="CortexA8" :LOR: "$Machine"="CortexA9" :LOR: "$Machine"="ARM11ZF" :LOR: "$Machine"="RPi"
        DCB     CDROMFSCMOS,        &C0                  ; drives = 0, buffer size = 256K
      |
        DCB     CDROMFSCMOS,        &C1                  ; drives = 1, buffer size = 256K
      ]
 ]
        DCB     &FF
        ALIGN

        END