; Copyright 2001 Pace Micro Technology plc
;
; 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.
;
; 19-Feb-01  KJB   Separated IIC operations from NVMemory and RTC code


PollMax		*	150	; Number of times to poll for an Ack (increase if you clock faster - need to
                                ; allow 5ms for write cycle).


; Choose a lower limit on the number of ticks per clock phase based on the
; MaxI2Cspeed variable defined in Hdr:Machine.<Machine>
  [ MaxI2Cspeed >= 1000
I2Cticks        *       1
  |
  [ MaxI2Cspeed >= 400
I2Cticks        *       3
  |
I2Cticks        *       10
  ]
  ]

IICOpSWI
        Push    LR
        BL      IIC_OpV
        Pull    LR
        B       SLVK_TestV


; *****************************************************************************

; in:   R0 = device address (bit 0 set => read, clear => write)
;       R1 -> data block
;       R2 = length of data block
;
IIC_Op
        Push    "R0-R2,LR"
        Push    "R0-R2"                 ; soft copy for IIC_OpV to work on
        MOV     R0, R13
        MOV     R1, #1
        BL      IIC_OpV
        ADD     R13, R13, #12           ; junk soft copy
        Pull    "R0-R2,PC",VC
        ADD     R13, R13, #4
        Pull    "R1-R2,PC"

; *****************************************************************************

; in:   R0 -> array of transfer descriptors
;       R1 = number of transfers
;
; out:  transfer descriptors may be updated (beware)
;
; transfer descriptor is 3 words: word 0 = device address (+direction)
;                                         (bit 29 signifies retry for response)
;                                         (bit 30 signifies checksum read only - ie fill in word 1 with
;                                          sum of bytes read)
;                                         (bit 31 signifies continued transfer - ie no start or address)
;                                 word 1 -> data block
;                                 word 2 = length of data block
IIC_OpV ROUT
        Push    "R0-R7,R10,R11,LR"
        MOV     R7, #0
        MRS     R6, CPSR
        BIC     R5, R6, #I32_bit
        MSR     CPSR_c, R5              ; enable interrupts - this may take some time

        MOV     R4, R0
        MOV     R5, R1

05      SUBS    R5, R5, #1
        BCC     %FT90

        LDMIA   R4!, {R0-R2}
        TST     R0, #1:SHL:31           ; skip start?
        BNE     %FT08

        TEQ     R7,#0
        BLEQ    Start
        TEQ     R7,#0
        BLNE    RepeatedStart

        TST     R0, #1:SHL:29
        BNE     %FT06
        BL      TXAck                   ; transmit device address without retries
        B       %FT07

06      BL      TXPollAck               ; transmit device address with retries
07      BVS     %FT80

08      MOV     R7, #1
        MOV     R11, R1
        MOVS    R10, R2
        BEQ     %BT05

        TST     R0, #1                  ; Z => write, NZ => read
        BNE     %FT20

; Write case
10      LDRB    R0, [R11], #1           ; read byte from data block
        BL      TXAck                   ; transmit, checking for ack
        BVS     %FT80
        SUBS    R10, R10, #1            ; decrement byte count
        BNE     %BT10                   ; loop until finished
        B       %BT05                   ; then next transfer

20      TST     R0, #1:SHL:30           ; checksum?
        BNE     %FT30

; Read case
21      BL      RXByte                  ; read byte from bus
        STRB    R0, [R11], #1           ; store in data block
        SUBS    R10, R10, #1            ; is it last byte ?
        MOVNE   R0, #0                  ; no, then acknowledge with 0 bit
        MOVEQ   R0, #1                  ; yes, then don't acknowledge
        BL      ClockData               ; but always send ack clock pulse
        BNE     %BT21
        B       %BT05                   ; next transfer

; Checksum case
30      MOV     R11, #0
31
        BL      RXByte                  ; read byte from bus
        ADD     R11, R11, R0
        SUBS    R10, R10, #1
        MOVNE   R0, #0                  ; no, then acknowledge with 0 bit
        MOVEQ   R0, #1                  ; yes, then don't acknowledge
        BL      ClockData               ; but always send ack clock pulse
        BNE     %BT31
        STR     R11, [R4, #-8]          ; store checksum
        B       %BT05                   ; next transfer

90      BL      Stop
        CLRV
        MSR     CPSR_c, R6              ; restore interrupts
        Pull    "R0-R7,R10,R11,PC"

80      BL      Stop
        ADR     R0, ErrorBlock_IIC_NoAcknowledge
 [ International
        BL      TranslateError
 |
        SETV
 ]
        ADD     R13, R13, #4
        Pull    "R1-R7,R10,R11,PC"

        MakeErrorBlock IIC_NoAcknowledge

; *****************************************************************************
;
;	SetC1C0 - Set clock and data lines to values in R1 and R0 respectively
;
; out:	ATPCS
;

SetC1C0 ROUT
        Push    "R9,R14"
 [ HAL
        AddressHAL
        MOV     R2, R1
        MOV     R1, R0
        MOV     R0, #0
        CallHAL HAL_IICSetLines
 |
	ADD	R0, R0, R1, LSL #1	; R0 := C0 + C1*2

	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

	MOV	R2, #IOC
	STRB	R0, [R2, #IOCControl]
 ]

      [ E2ROMSupport
        MOV	R0, #0
        LDRB	R0, [R0, #NVRamSpeed]
        TEQ     R0, #0
        MOVEQ   R0, #10                         ; default value if speed not checked yet
      |
        MOV     R0, #10                         ; default to slowest value if we have E2ROMSupport is false
      ]
	BL	DoMicroDelay

        Pull    "R9,PC"

; *****************************************************************************
;
;	ReadC1C0 - Read clock and data lines to  R1 and R0 respectively
;
; out:	R0, R1 updated - otherwise ATPCS
;

ReadC1C0 ROUT
 [ HAL
        Push    "sb,lr"
        AddressHAL
        CallHAL HAL_IICReadLines
        Pull    "sb,pc"
 |
        MOV     a3, #IOC
        LDRB    a1, [a3, #IOCControl]
        MOV     a2, a1, LSR #1
        AND     a1, a1, #1
        AND     a2, a2, #1
        MOV     pc, lr
 ]

; *****************************************************************************
;
;	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
  [ HAL
        Push    "a3,a4,sb,ip,lr"
        AddressHAL
        MOVS    a1, a1, LSR #1
        ADC     a1, a1, #0
        CallHAL HAL_CounterDelay
        Pull    "a3,a4,sb,ip,pc"
  |
        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
 [ No26bitCode
	Push	"R0-R3,R4,R5,R12,R14"
        MRS     R4,CPSR
 |
	Push	"R0-R3,R5,R12,R14"
 ]
        MOV     R5, R0

	MOV	R1, #0			; Clock lo
	BL	SetC1C0

 [ No26bitCode
        ORR     R1,R4,#I32_bit
        MSR     CPSR_c,R1
 |
; 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     R0, R5
	MOV	R1, #1			; Clock hi
	BL	SetC1C0

; Delay here must be >= 4.0 microsecs

        MOV     R0, R5
	MOV	R1, #0			; Clock lo
	BL	SetC1C0

 [ No26bitCode
        MSR     CPSR_cf,R4              ; Restore interrupts
        Pull    "R0-R3,R4,R5,R12,PC"
 |
	Pull	"R0-R3,R5,R12,PC",,^
 ]

ClockData0 ROUT				; Clock a zero bit
	Push	"R0, R14"
	MOV	R0, #0
	BL	ClockData               ; preserves PSR
	Pull	"R0, PC"

; *****************************************************************************
;
;	Start - Send the Start signal
;
; out:	All registers preserved, including PSR
;

Start	ROUT
 [ No26bitCode
	Push	"R0-R3,R4,R12,R14"
        MRS     R4, CPSR
 |
	Push	"R0-R3,R12,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

; Delay here must be >= 4.0 microsecs (0.6 for fast device)

	MOV	R0, #0			; clock LO, data LO
	MOV	R1, #0
	BL	SetC1C0

 [ No26bitCode
        MSR     CPSR_f, R4
	Pull	"R0-R3,R4,R12,PC"
 |
	Pull	"R0-R3,R12,PC",,^
 ]

; *****************************************************************************
;
;	RepeatedStart - Send a Repeated Start signal
;
; out:	All registers preserved, including PSR
;

RepeatedStart	ROUT
 [ No26bitCode
	Push	"R0-R3,R4,R12,R14"
        MRS     R4, CPSR
 |
	Push	"R0-R3,R12,R14"
 ]

        MOV     R0, #1
        MOV     R1, #0                  ; clock LO, data HI
        BL      SetC1C0

	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

; Delay here must be >= 4.0 microsecs (0.6 for fast device)

	MOV	R0, #0			; clock LO, data LO
	MOV	R1, #0
	BL	SetC1C0

 [ No26bitCode
        MSR     CPSR_f, R4
	Pull	"R0-R3,R4,R12,PC"
 |
	Pull	"R0-R3,R12,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-R4,R12,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

        MRS     R1,CPSR
        Push    "R1"
        ORR     R1,R1,#I32_bit
        MSR     CPSR_c,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)

        BL      ReadC1C0
        MOV     R4, R0                  ; should be LO for correct acknowledge

	MOV  	R0, #1
	MOV  	R1, #0			; clock LO, data HI
	BL  	SetC1C0

 [ {TRUE}
	Pull	"R1"
	MSR     CPSR_c,R1
 ]

        TST     R4, #1
        MRS     R2, CPSR
	BICEQ	R2, R2, #V_bit		; clear V if correct acknowledge
	ORRNE	R2, R2, #V_bit		; set V if no acknowledge
        MSR     CPSR_f, R2

	Pull	"R0-R4,R12,PC"

; *****************************************************************************
;
;	Stop - Send the Stop signal
;
; out:	All registers preserved, including PSR
;

Stop	ROUT
 [ No26bitCode
	Push	"R0-R3,R4,R12,R14"
        MRS     R4, CPSR
 |
	Push	"R0-R3,R12,R14"
 ]

	MOV	R0, #0			; clock LO, data LO
	MOV	R1, #0
	BL	SetC1C0

	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

 [ No26bitCode
        MSR     CPSR_f, R2
	Pull	"R0-R3,R4,R12,PC"
 |
	Pull	"R0-R3,R12,PC",,^
 ]

; *****************************************************************************
;
;	TXByte - Transmit a byte
;
; in:	R0 = byte to be transmitted
;
; out:	All registers preserved, including PSR
;

TXByte	ROUT
 [ No26bitCode
	Push	"R0-R3,R14"
        MRS     R3, CPSR
 |
	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
 [ No26bitCode
        MSR     CPSR_f, R3
	Pull	"R0-R3,PC"
 |
	Pull	"R0-R2,PC",,^
 ]

TXAck	ROUT
	Push	R14
	BL	TXByte
        Pull    R14
	B	Acknowledge


; *****************************************************************************
;
;	TXPollAck - Transmit 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
;

TXPollAck	ROUT
	Push	"R1,R14"
	MOV	R1, #1
10
	BL	TXByte
	BL	Acknowledge
        Pull    "R1,PC",VC
        ADD     R1, R1, #1
	TEQ	R1, #PollMax
        BEQ     %FT90
 [	{FALSE}
	BREG	R1, "i2c tries:"
 ]
        BL      RepeatedStart
        B       %BT10
90
	Pull	"R1,PC"

; *****************************************************************************
;
;	RXByte - Receive a byte
;
; out:	R0 = byte received
;	All other registers preserved, including PSR
;

RXByte	ROUT
 [ No26bitCode
	Push	"R1-R6,R12,R14"
        MRS     R5, CPSR
 |
	Push	"R1-R4, R6, R12, R14"
 ]
	MOV	R6, #0			; byte:=0
	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

        MRS     R1,CPSR
        Push    "R1"
        ORR     R1,R1,#I32_bit
        MSR     CPSR_c,R1
 ]
	MOV	R0, #1			; pulse clock HI
	MOV	R1, #1
	BL	SetC1C0

        BL      ReadC1C0
        ADD     R6, R0, R6, LSL #1      ; byte:=byte*2+ SDA

	MOV	R0, #1			; return clock LO
	MOV	R1, #0
	BL	SetC1C0

 [ {TRUE}
	Pull	"R1"
	MSR     CPSR_c,R1
 ]
	SUBS	R4, R4, #1
	BCS	%BT10

	MOV	R0, R6			; return the result in R0
 [ No26bitCode
        MSR     CPSR_f, R5
	Pull	"R1-R6, R12, PC"
 |
	Pull	"R1-R4, R6, R12, PC",,^
 ]

        END