; 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. ; amg 7/12/96 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) PollMax * 150 ; Number of times to poll for an Ack (increase if you clock faster - need to ; allow 5ms for write cycle). ; Device addresses RTCAddress * &a0 ; traditional RTC / 240 byte CMOS [ E2ROMSupport E2ROMAddress * &a8 ; 24C08 device - 512 byte or 1K E2ROMAddress2K * &e0 ; 24C174 device - 2K E2ROMAddress2K_OTP * &60 ; 24C174 device - OTP section E2ROMAddress4K * &a4 ; 24C32 device - 4K (top 1K protectable) E2ROMAddress8K * &a2 ; 24C64 device - 8K (top 2K protectable) E2ROMAddress16K * &a8 ; 24C128 device - 16K ] ; ***************************************************************************** ; ; 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" ; ***************************************************************************** ; ; SetC1C0 - Set clock and data lines to values in R1 and R0 respectively ; ; out: All registers preserved, including PSR ; SetC1C0 ROUT Push "R0-R2,R14" ADD R0, R0, R1, LSL #1 ; R0 := C0 + C1*2 [ AssemblingArthur :LOR: Module MOV R2, #0 ; prepare to index soft copy LDRB R1, [R2, #IOCControlSoftCopy] ; read soft copy BIC R1, R1, #&03 ; clear clock and data ORR R0, R1, R0 ; put in new clock and data ORR R0, R0, #&C0 ; make sure two test bits are ; always set to 1 ! STRB R0, [R2, #IOCControlSoftCopy] ; store back to soft copy | ORR R0, R0, #&FC ; set other bits to 1 ] MOV R2, #IOC STRB R0, [R2, #IOCControl] [ ClockNVMemoryFast [ :LNOT: :DEF: TestHarness MOV R0, #0 LDRB R0, [R0, #NVRamSpeed] | LDRB R0, NVSpeed ] TEQ R0, #0 MOVEQ R0, #10 ; default value if speed not checked yet | MOV R0, #10 ; 5µs delay ] BL DoMicroDelay Pull "R0-R2,PC",,^ ; ***************************************************************************** ; ; DoMicroDelay - Delay for >= R0/2 microseconds ; ; in: R0 = time delay in 1/2 microsecond units ; R2 -> IOC ; On ARM600, we may or may not be in a 32-bit mode ; ; out: R0,R1 corrupted ; DoMicroDelay ROUT Push R14 STRB R0, [R2, #Timer0LR] ; copy counter into output latch LDRB R1, [R2, #Timer0CL] ; R1 := low output latch 10 STRB R0, [R2, #Timer0LR] ; copy counter into output latch LDRB R14, [R2, #Timer0CL] ; R14 := low output latch TEQ R14, R1 ; unchanged ? BEQ %BT10 ; then loop MOV R1, R14 ; copy anyway SUBS R0, R0, #1 ; decrement count BNE %BT10 ; loop if not finished Pull PC LTORG ; ***************************************************************************** ; ; ClockData - Clock a bit of data down the IIC bus ; ; in: R0 = data bit ; ; out: All registers preserved, including PSR ; ClockData ROUT Push "R1, R14" MOV R1, #0 ; Clock lo BL SetC1C0 [ {TRUE} ; Disable interrupts to ensure clock hi with data hi is only transient ; This allows BMU to detect idle condition by polling MOV R1,PC ORR R1,R1,#I_bit TEQP PC,R1 ] MOV R1, #1 ; Clock hi BL SetC1C0 ; Delay here must be >= 4.0 microsecs MOV R1, #0 ; Clock lo BL SetC1C0 Pull "R1, PC",,^ ClockData0 ROUT ; Clock a zero bit Push "R0, R14" MOV R0, #0 BL ClockData Pull "R0, PC",,^ ; ***************************************************************************** ; ; Start - Send the Start signal ; ; out: All registers preserved, including PSR ; Start ROUT Push "R0-R2,R14" MOV R0, #1 ; clock HI, data HI MOV R1, #1 BL SetC1C0 ; Delay here must be >= 4.7 microsecs (1.3 for fast device) MOV R0, #0 ; clock HI, data LO MOV R1, #1 BL SetC1C0 [ :LNOT: ClockNVMemoryFast ; Hold start condition for BMU MOV R2, #IOC MOV R0, #10 BL DoMicroDelay ] ; Delay here must be >= 4.0 microsecs (0.6 for fast device) MOV R0, #0 ; clock LO, data LO MOV R1, #0 BL SetC1C0 Pull "R0-R2,PC",,^ ; ***************************************************************************** ; ; Acknowledge - Check acknowledge after transmitting a byte ; ; out: All registers preserved ; V=0 => acknowledge received ; V=1 => no acknowledge received ; Acknowledge ROUT Push "R0-R2,R14" MOV R0, #1 ; clock LO, data HI MOV R1, #0 BL SetC1C0 [ {TRUE} ; Disable interrupts to ensure clock hi with data hi is only transient ; This allows BMU to detect idle condition by polling MOV R1,PC Push "R1" ORR R1,R1,#I_bit TEQP PC,R1 ] MOV R0, #1 ; clock HI, data HI MOV R1, #1 BL SetC1C0 ; Delay here must be >= 4.0 microsecs (0.6 for fast device) MOV R2, #IOC LDRB R2, [R2, #IOCControl] ; get the data from IOC MOV R0, #0 MOV R1, #0 ; clock lo BL SetC1C0 [ {TRUE} Pull "R1" TEQP PC,R1 ] TST R2, #1 ; should be LO for correct acknowledge MOV R2, PC BICEQ R2, R2, #V_bit ; clear V if correct acknowledge ORRNE R2, R2, #V_bit ; set V if no acknowledge TEQP R2, #0 Pull "R0-R2,PC" ; ***************************************************************************** ; ; Stop - Send the Stop signal ; ; out: All registers preserved, including PSR ; Stop ROUT Push "R0,R1,R14" MOV R0, #0 ; clock HI, data LO MOV R1, #1 BL SetC1C0 ; Delay here must be >= 4.0 microsecs (0.6 for fast device) MOV R0, #1 ; clock HI, data HI MOV R1, #1 BL SetC1C0 Pull "R0,R1,PC",,^ ; ***************************************************************************** ; ; TXByte - Transmit a byte ; ; in: R0 = byte to be transmitted ; ; out: All registers preserved, including PSR ; TXByte ROUT Push "R0-R2,R14" MOV R1, #&80 ; 2^7 the bit mask MOV R2, R0 ; byte goes into R2 10 ANDS R0, R2, R1 ; zero if bit is zero MOVNE R0, #1 BL ClockData ; send the bit MOVS R1, R1, LSR #1 BNE %BT10 Pull "R0-R2,PC",,^ ; ***************************************************************************** ; ; StartTXPollAck - Transmit start and a byte and poll for acknowledge ; ; This is intended for devices with a slow internal write cycle which ; don't ack until the write cycle is finished ( eg ATMEL AT24C01A/x ) ; ; in: R0 = byte to be transmitted ; ; out: All registers preserved, including PSR ; StartTXPollAck ROUT Push "R1,R14" MOV R1, #0 10 INC R1 CMP R1, #PollMax Pull "R1,pc", EQ BL Start BL TXByte BL Acknowledge BVS %BT10 [ {FALSE} BREG R1, "i2c tries:" ] Pull "R1,pc",,^ ; ***************************************************************************** ; ; RXByte - Receive a byte ; ; out: R0 = byte received ; All other registers preserved, including PSR ; RXByte ROUT Push "R1-R4, R14" MOV R3, #0 ; byte:=0 MOV R2, #IOC MOV R4, #7 MOV R0, #1 ; clock LO, data HI MOV R1, #0 BL SetC1C0 10 [ {TRUE} ; Disable interrupts to ensure clock hi with data hi is only transient ; This allows BMU to detect idle condition by polling MOV R1,PC Push "R1" ORR R1,R1,#I_bit TEQP PC,R1 ] MOV R0, #1 ; pulse clock HI MOV R1, #1 BL SetC1C0 LDRB R1, [R2, #IOCControl] ; get the data from IOC AND R1, R1, #1 ADD R3, R1, R3, LSL #1 ; byte:=byte*2+(IOC?0)AND1 MOV R0, #1 ; return clock LO MOV R1, #0 BL SetC1C0 [ {TRUE} Pull "R1" TEQP PC,R1 ] SUBS R4, R4, #1 BCS %BT10 MOV R0, R3 ; return the result in R0 Pull "R1-R4, PC",,^ ; ***************************************************************************** HTBTA ROUT Push R14 BL HexToBCD BL TXAck Pull PC TXAck ROUT Push R14 BL TXByte BL Acknowledge Pull PC CD0RBTH ROUT Push R14 BL ClockData0 BL RXByte BL BCDToHex 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 ] Pull "R1-R4,R14" ORRS PC, LR, #V_bit MakeErrorBlock CoreNotWriteable 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 ] MOV R14, #0 ; 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 [ ChecksumCMOS BL ReadStraight ; calculate new checksum : MOV R4, R0 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 [ CacheCMOSRAM CMP r2, #&100 ; check small cache limit BCS %FT15 [ :LNOT: :DEF: TestHarness LDR R1, =CMOSRAMCache ; update cache, but always write to | ADR R1, i2cWorkSpace ] STRB R3, [R1, R2] ; real hardware as well 15 ] [ E2ROMSupport MOV R0, R2 BL GetI2CAddress ; convert to device address + offset MOV R2, R0 ; save the offset | MOV R1, #RTCAddress ] MOV R0, R1 ; device address for write BL StartTXPollAck TST R1, #&100 ; 2-byte address? MOVNE R0, R2, LSR #8 ; offset (MSB) BLNE TXAck AND R0, R2, #&FF ; offset (LSB) BL TXAck MOV R0, R3 ; data BL TXAck BL Stop [ 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 [ :LNOT: :DEF: TestHarness MOV R14, #0 LDRB R4, [R14, #NVRamWriteSize] LDRB R14, [R14, #NVRamSize] MOV R4, R4, LSL #8 | LDRB R14, NVSize MOV R4, R14 ] 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 BHS %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 ] 95 Pull "R1-R4,LR" ORRS PC, LR, #V_bit ; ***************************************************************************** ; ; 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. [ :LNOT: :DEF: TestHarness MOV R14, #0 LDRB R14, [R14, #NVRamPageSize] | LDRB R14, NVPageSize ] MOV R5, #1 MOV R14, R5, LSL R14 ; R14 = (1<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) [ CacheCMOSRAM TEQ R0, #0 ; check it's a cacheable segment BEQ %FT15 CMP R0, #&100 BHS %FT15 [ :LNOT: :DEF: TestHarness LDR R14, =CMOSRAMCache | ADR R14, i2cWorkSpace ] 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",,^ ] 15 Push "R0-R2" ADD R0, R0, R4 ; R0 = physical address [ E2ROMSupport BL GetI2CAddress ; convert to device address and offset | MOV R1, #RTCAddress ] MOV R2, R0 ; save the offset MOV R0, R1 ; device address for write BL StartTXPollAck TST R1, #&100 ; 2-byte address? MOVNE R0, R2, LSR #8 ; offset (MSB) BLNE TXAck AND R0, R2, #&FF ; offset (LSB) BL TXAck BL Start ADD R0, R1, #1 ; device address for read BL TXAck Pull "R0-R2" SUB R5, R3, R0 ; R5 = bytes being read ADD R0, R0, R5 ; update return R0 SUB R2, R2, R5 ; update return R2 Push "R0,R2" 20 BL RXByte STRB R0, [R1], #1 SUBS R5, R5, #1 MOVNE R0, #0 ; not done - ACK byte MOVEQ R0, #1 ; done - no ACK BL ClockData BNE %BT20 BL Stop 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) [ CacheCMOSRAM TEQ R0, #0 ; check it's a cacheable segment BEQ %FT15 CMP R0, #&100 BHS %FT15 [ :LNOT: :DEF: TestHarness LDR R14, =CMOSRAMCache | ADR R14, i2cWorkSpace ] 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 address [ E2ROMSupport BL GetI2CAddress ; convert to device address and offset | MOV R1, #RTCAddress ] MOV R2, R0 ; save the offset MOV R0, R1 ; device address for write BL StartTXPollAck TST R1, #&100 ; 2-byte address? MOVNE R0, R2, LSR #8 ; offset (MSB) BLNE TXAck AND R0, R2, #&FF ; offset (LSB) BL TXAck BL Start ADD R0, R1, #1 ; device address for read BL TXAck Pull "R0-R2" SUB R5, R3, R0 ; R5 = bytes being read ADD R0, R0, R5 ; update return R0 SUB R2, R2, R5 ; update return R2 Push "R0,R2" 20 BL RXByte ADD R1, R1, R0 SUBS R5, R5, #1 MOVNE R0, #0 ; not done - ACK byte MOVEQ R0, #1 ; done - no ACK BL ClockData BNE %BT20 BL Stop 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" [ :LNOT: :DEF: TestHarness MOV R14, #0 ; get no 256 byte blocks and calculate end address LDRB R14, [R14, #NVRamSize] | LDRB R14, NVSize ] MOV R14, R14, LSL #8 CMP R0, R14 Pull "R14",CS ORRCSS PC, R14, #C_bit ; indicate invalid ; address is < end address -> is valid [ :LNOT: :DEF: TestHarness MOV R1, #0 LDRB R1, [R1, #RTCFitted] | LDRB R1, RTCFlag ] TEQ R1, #0 MOVNE R1, #RTCAddress [ :LNOT: :DEF: TestHarness LDREQB R1, [R1, #NVRamBase] | LDREQB R1, NVBase ] 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 Pull "R14" BICS PC, R14, #C_bit ; 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" [ :LNOT: :DEF: TestHarness MOV R14, #0 ; read no 256 byte blocks and calculate end address LDRB R14, [R14, #NVRamSize] | LDRB R14, NVSize ] MOV R14, R14, LSL #8 CMP R0, R14 ; if >= end address then Pull "R14" ORRCSS PC, R14, #C_bit ; invalid CMP R0, #&100 ; if < end address && >= &100 then BICCSS PC, R14, #C_bit ; valid (no mungeing) ] CMP R0, #&F0 ; if < &100 && >= &f0 then [ E2ROMSupport SUBCS R0, R0, #&F0 ; map &F0->&FF to &00->0F for OTP section BICCSS PC, R14, #C_bit | ORRCSS PC, R14, #C_bit ; invalid ] ADD R0, R0, #&40 ; now in range &40..&13F CMP R0, #&100 SUBCS R0, R0, #(&100-&10) ; now in range &40..&FF, &10..&3F BICS PC, R14, #C_bit ; 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 [ :LNOT: :DEF: TestHarness MOV R2, #0 ; read number of 256 byte blocks and calculate end address LDRB R2, [R2, #NVRamSize] | LDRB R2, NVSize ] MOV R2, R2, LSL #8 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 [ :LNOT: :DEF: TestHarness MOV R2, #0 LDRB R2, [R2, #NVRamSize] | LDRB R2, NVSize ] MOV R2, R2, LSL #8 BL ChecksumBlock MOV R0, #CheckSumCMOS BL Write Pull "R1-R2,PC" ] LTORG ; ***************************************************************************** ; ; SetTime - Write the CMOS clock time and update 5-byte RealTime ; ; in: UTC time: ; R0 = hours ; R1 = minutes ; R2 = day of month ; R3 = month ; R5 = year (lo) ; R6 = year (hi) ; R7 = seconds ; R8 = centiseconds ; ; Any of the above, if -1, will not be written to ; SetTime ROUT Push "R4, R14" ; save registers [ :LNOT: :DEF: TestHarness MOV R14, #0 LDRB R14, [R14, #RTCFitted] | LDRB R14, RTCFlag ] TEQ R14, #0 BNE %FT20 ; no RTC - just set soft copy [ :LNOT: :DEF: TestHarness BL RegToRealTime ] Pull "R4, PC" 20 ; write year to CMOS RAM MOV R4, R0 ; save hours in R4 Push "R1" MOVS R1, R5 MOVPL R0, #YearCMOS BLPL Write MOVS R1, R6 MOVPL R0, #YearCMOS+1 BLPL Write CMP R4, #-1 ; are we writing time ? Pull "R1",EQ ; [no, then skip] BEQ %FT30 MOV R0, #&01 ; start at address 1 [ E2ROMSupport BL GetI2CAddress ; convert to device address and offset MOV R0, R1 ; write address | MOV R0, #RTCAddress ] Pull "R1" BL StartTXPollAck MOV R0, #&01 ; offset 1 BL TXAck MOV R0, R8 ; centiseconds BL HTBTA MOV R0, R7 ; seconds BL HTBTA MOV R0, R1 ; minutes BL HTBTA MOV R0, R4 ; hours BL HTBTA BL Stop 30 CMP R2, #-1 ; are we writing date ? BEQ %FT40 ; [no, then skip] MOV R0, #&05 ; start at address 5 [ E2ROMSupport BL GetI2CAddress ; convert to device address and offset MOV R0, R1 ; write address | MOV R0, #RTCAddress ] BL StartTXPollAck MOV R0, #&05 ; offset 5 BL TXAck MOV R0, R2 ; day of month BL HexToBCD ORR R0, R0, R5, LSL #6 ; year in bits 6,7; day in bits 0..5 BL TXAck MOV R0, R3 ; months BL HTBTA BL Stop 40 MOV R0, R4 ; put hours back in R0 [ :LNOT: :DEF: TestHarness BL RTCToRealTime ] Pull "R4, PC" ; ***************************************************************************** ; ; ReadTime - Read the CMOS clock time ; ; in: - ; ; out: R0 = hours ; R1 = minutes ; R2 = days ; R3 = months ; R5 = year (lo) ; R6 = year (hi) ; R7 = seconds ; R8 = centiseconds ; ReadTime ROUT Push "R4, R14" MOV R0, #&01 ; start at address 1 [ E2ROMSupport BL GetI2CAddress ; convert to device address and offset MOV R0, R1 ; write address | MOV R0, #RTCAddress ] BL StartTXPollAck MOV R0, #&01 ; offset 1 BL TXAck BL Start [ E2ROMSupport ADD R0, R1, #1 ; read address | MOV R0, #(RTCAddress+1) ; read address ] BL TXAck BL RXByte BL BCDToHex MOV R8, R0 ; centiseconds BL CD0RBTH MOV R7, R0 ; seconds BL CD0RBTH MOV R1, R0 ; minutes BL CD0RBTH MOV R4, R0 ; hours BL ClockData0 BL RXByte AND R0, R0, #&3F ; day of month (clear year bits) BL BCDToHex MOV R2, R0 BL ClockData0 BL RXByte AND R0, R0, #&1F ; month (clear day of week bits) BL BCDToHex MOV R3, R0 MOV R0, #1 BL ClockData BL Stop MOV R0, #YearCMOS BL Read MOV R5, R0 ; year (lo) MOV R0, #YearCMOS+1 BL Read MOV R6, R0 ; year (hi) MOV R0, R4 ; put hours in R0 ; Ensure day/month are non-zero, fixes RTCToRealTime [ {TRUE} ; LRust, fix RP-0370 TEQ R2, #0 ; Valid day? MOVEQ R2, #1 ; No then force 1st TEQ R3, #0 ; Invalid month? MOVEQ R3, #1 ; Yes then force Jan ] Pull "R4, PC" ; ***************************************************************************** ; ; InitCMOSCache - Initialise cache of CMOS RAM [ CacheCMOSRAM InitCMOSCache ENTRY "r0-r6" [ E2ROMSupport ; Need to set the slowest speed so we can probe MOV R2, #0 [ ClockNVMemoryFast MOV R3, #10 ; Default speed setting (5µs delays) [ :LNOT: :DEF: TestHarness STRB R3, [R2, #NVRamSpeed] | STRB R3, NVSpeed ] ] ; First determine what hardware we've got fitted, R4 holds the number of ; 256 byte blocks that we've found ; Have we got an RTC ? MOV R0, #RTCAddress BL DummyAccess MOVVS R4, #0 MOVVC R4, #1 [ :LNOT: :DEF: TestHarness STRB R4, [R2, #RTCFitted] | STRB R4, RTCFlag ] MOV R5, #4 ; assume 16 byte page size to start with MOV R6, #0 ; assume not protected ; Have we got a 2K E² ? MOV r1, #E2ROMAddress2K MOV r0, #(E2ROMAddress2K+14) BL DummyAccess MOVVC R4, #8 [ ClockNVMemoryFast MOVVC R3, #3 ; Fast speed setting (1.5µs delays) ] BVC %FT5 ; Have we got a 1K E² ? MOV r1, #E2ROMAddress MOV r0, #(E2ROMAddress+6) BL DummyAccess MOVVC R4, #4 BVC %FT5 ; Have we got 512 bytes of E² ? MOV r0, #(E2ROMAddress+2) BL DummyAccess MOVVC R4, #2 BVC %FT5 ; Have we got a 16K device ? (Note that this probe would make a 24C08 device respond - ; but if we've gotten this far it would have to be a < 512 byte version; we've never ; used such a device). MOV r1, #E2ROMAddress16K MOV r0, #E2ROMAddress16K BL DummyAccess MOVVC R4, #64 MOVVC R5, #6 ; 64 byte page size [ ClockNVMemoryFast 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 [ IOMD_C_EEPROMProtect <> 0 MOVVC R6, #12 ; Only bottom 3K writable ] MOVVC R5, #5 ; 32 byte page size [ ClockNVMemoryFast 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 [ IOMD_C_EEPROMProtect <> 0 MOVVC R6, #24 ; Only bottom 6K writable ] MOVVC R5, #5 ; 32 byte page size [ ClockNVMemoryFast MOVVC R3, #3 ; Fast speed setting (1.5µs delays) ] MOVVS R1, #RTCAddress MOVVS R5, #8 ; 256 byte page size for CMOS 5 ; Set the NVRam count [ :LNOT: :DEF: TestHarness STRB R1, [R2, #NVRamBase] STRB R4, [R2, #NVRamSize] STRB R5, [R2, #NVRamPageSize] TEQ R6, #0 MOVEQ R6, R4 STRB R6, [R2, #NVRamWriteSize] [ ClockNVMemoryFast STRB R3, [R2, #NVRamSpeed] ] LDR R3, =CMOSRAMCache | STRB R1, NVBase STRB R4, NVSize STRB R5, NVPageSize [ ClockNVMemoryFast STRB R3, NVSpeed ] ADR R3, i2cWorkSpace ] ; Initialise the cache 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 | MOV R1, #RTCAddress MOV R2, #&10 MOV R4, #&100 ; stop at address &100 [ :LNOT: :DEF: TestHarness LDR R3, =CMOSRAMCache | ADR R3, i2cWorkSpace ] ] 09 BL Start MOV R0, R1 ; write address BL TXAck TST R1, #&100 ; 2-byte address? MOVNE R0, R2, LSR #8 ; memory word address (MSB) BLNE TXAck AND R0, R2, #&FF ; memory word address (LSB) BL TXAck BL Start ORR R0, R1, #1 ; read address BL TXAck 10 BL RXByte ; returned in R0 STRB R0, [R3, R2] ADD R2, R2, #1 ; increment R2 to phys. address CMP R2, R4 ; stop when we hit end address BGE %FT20 TST R2, #&FF ; still in same 256 byte block? MOVNE R0, #0 ; yes => not done .. ACK that byte MOVEQ R0, #1 ; no => finish off reading block BL ClockData BNE %BT10 ; yes => continue reading [ E2ROMSupport BL Stop MOV R0, R2 ; in next 256 byte block so get device + address BL GetI2CAddress B %BT09 ; and start next block ] 20 MOV R0, #1 ; finish off reading block BL ClockData BL Stop [ E2ROMSupport TEQ R4, #&100 ; have we reached the end? EXIT EQ ; exit if so MOV R0, R4 ; start next block from R4 MOV R4, #&100 ; stop at &100 BL GetI2CAddress ; convert to device address and offset MOV R2, R0 ; save the offset B %BT09 ; and go round again | EXIT ] [ E2ROMSupport ReadOTPArea Entry BL Start MOV r0, #E2ROMAddress2K_OTP BL TXAck MOV r0, #0 ; OTP memory word address BL TXAck BL Start MOV r0, #E2ROMAddress2K_OTP + 1 ; switch to read BL TXAck MOV r2, #0 10 BL RXByte STRB r0, [r3, r2] ADD r2, r2, #1 TEQ r2, #16 MOVNE r0, #0 ; not done, so clock zero MOVEQ r0, #1 BL ClockData BNE %BT10 BL Stop 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 ; Have we got an RTC ? do a read from location 0 to find out [ E2ROMSupport DummyAccess ENTRY BL Start BL TXAck ; do write and set V if no ack BL Stop ; Doesn't change the PSR 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, #4 ADDLS PC, PC, R0, LSL #2 B NVMemory_Unknown B NVMemory_Size B NVMemory_Read B NVMemory_Write B NVMemory_ReadBlock B NVMemory_WriteBlock NVMemory_Unknown ADRL R0, ErrorBlock_HeapBadReason [ International Push LR BL TranslateError Pull LR ] ORRS PC, LR, #V_bit ; ----------------------------------------------------------------------------- ; OS_NVMemory 0 - find NV memory size ; ; in: R0 = 0 ; ; out: R1 = NV memory size in bytes ; NVMemory_Size [ E2ROMSupport LDRB R1, [R0, #NVRamSize] MOV R1, R1, LSL #8 | MOV R1, #240 ] MOVS PC, LR ; ----------------------------------------------------------------------------- ; OS_NVMemory 1 - read a byte ; ; in: R0 = 1 ; R1 = location ; ; out: R2 = value ; NVMemory_Read ENTRY "R4" MOV R4, PC TEQP PC, #SVC_mode ; 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 TEQP PC, R4 ; restore interrupt state EXIT ; ----------------------------------------------------------------------------- ; OS_NVMemory 2 - write a byte ; ; in: R0 = 1 ; R1 = location ; R2 = value ; NVMemory_Write ROUT [ ProtectStationID TEQ R1, #0 ; just ignore writes to byte 0 MOVEQ PC, R14 ] ENTRY "R1,R4" MOV R4, PC TEQP PC, #SVC_mode ; 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 TEQP PC, 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" MOV R4, PC TEQP PC, #SVC_mode ; 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 TEQP PC, R4 ; restore interrupt state EXIT ; ----------------------------------------------------------------------------- ; OS_NVMemory 4 - write a block ; ; in: R0 = 3 ; R1 = location ; R2 = buffer ; R3 = length ; NVMemory_WriteBlock ROUT ENTRY "R1-R4" MOV R4, PC TEQP PC, #SVC_mode ; enable interrupts - this may take some time [ ProtectStationID TEQ R1, #0 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 TEQP PC, R4 ; restore interrupt state EXIT END