; Copyright 2009 Castle Technology 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.
;

        GET     Hdr:ListOpts
        GET     Hdr:Macros
        GET     Hdr:System
        GET     Hdr:Machine.<Machine>
        GET     Hdr:ImageSize.<ImageSize>
        $GetIO

        GET     Hdr:OSEntries
        GET     Hdr:HALEntries

        GET     hdr.omap3530
        GET     hdr.StaticWS
        GET     hdr.Timers
        GET     hdr.PRCM

        AREA    |Asm$$Code|, CODE, READONLY, PIC

        EXPORT  I2C_Init

        EXPORT  HAL_IICBuses
        EXPORT  HAL_IICType
        EXPORT  HAL_IICDevice
        EXPORT  HAL_IICTransfer
        EXPORT  HAL_IICMonitorTransfer
        EXPORT  HAL_VideoIICOp
        EXPORT  IIC_DoOp_Poll

        IMPORT  HAL_CounterDelay

; The OMAP3530 has 4 I2C controllers:
; I2C1 - On the beagleboard this is connected to the TWL/TPS companion chip. RISC OS sees it as I2C bus 0.
; I2C2 - On the beagleboard this is routed to the expansion connector, and so has no use by default. RISC OS sees it as I2C bus 1.
; I2C3 - On the beagleboard this is routed to the HDMI connector. This means its sole use is for reading EDID/DDC data. RISC OS therefore accesses it via HAL_VideoIICOp.
; I2C4 - This is a bus that the OMAP does not fully expose to the programmer. Connected to the TWL/TPS, it's part of the PRCM system. RISC OS does not see this bus at all, and neither does this code.

                GBLL    I2CDebug
I2CDebug        SETL {FALSE} :LAND: Debug

                GBLL    I2CDebugData ; Display bytes sent & received?
I2CDebugData    SETL {FALSE} :LAND: I2CDebug

                GBLL    I2CDebugError ; Debug unexpected_error occurences
I2CDebugError   SETL {FALSE} :LAND: Debug


 [ I2CDebug :LOR: I2CDebugError
        IMPORT  DebugHALPrint
        IMPORT  DebugHALPrintReg
        IMPORT  DebugMemDump
        IMPORT  DebugHALPrintByte
 ]

I2C_Init
        Push    "v1-v3,lr"
 [ I2CDebug
        DebugTX "I2C_Init"
 ]
        ; 1. Make sure clocks are enabled
        LDR     a1, L4_ClockMan_Log
        LDR     a2, [a1, #CM_ICLKEN1_CORE]
        ORR     a2, a2, #7:SHL:15
        STR     a2, [a1, #CM_ICLKEN1_CORE]
        LDR     a2, [a1, #CM_FCLKEN1_CORE]
        ORR     a2, a2, #7:SHL:15
        STR     a2, [a1, #CM_FCLKEN1_CORE]
        ; Wait for power?
        MOV     a1, #50*1024 ; 50msec ish
        BL      HAL_CounterDelay
        ; 2. Initialise each I2C controller
        MOV     v1, #3
        ADR     v2, I2C_Table
10
 [ I2CDebug
        DebugReg  v1, "remaining busses: "
 ]
        LDR     v3, [v2, #I2C_HW]
        CMP     v3, #0
        BEQ     %FT30 ; Skip unused busses
        ; First we'll reset the controller
        LDRH    a4, [v3, #I2C_CON]
        TST     a4, #1:SHL:15
        BEQ     %FT20
        MOV     a4, #0
        STRH    a4, [v3, #I2C_CON]
        ; The manual makes no mention of it, but u-boot waits for a while after enabling & disabling each controller. So to play it safe I'll follow u-boot's lead.
        MOV     a1, #50*1024 ; 50msec ish
        BL      HAL_CounterDelay
20
        MOV     a4, #2
        STRH    a4, [v3, #I2C_SYSC]
        MOV     a4, #1:SHL:15
        STRH    a4, [v3, #I2C_CON]
        ; Wait
        MOV     a1, #10*1024 ; 10msec ish
        BL      HAL_CounterDelay
20
        LDRH    a4, [v3, #I2C_SYSS]
        TST     a4, #1
        BEQ     %BT20
        ; Now disable the controller again so we can program it properly
        MOV     a4, #0
        STRH    a4, [v3, #I2C_CON]
        ; Wait
        MOV     a1, #50*1024 ; 50msec ish
        BL      HAL_CounterDelay
        ; Set up the 12MHz sampling clock
        MOV     a4, #7
        STRH    a4, [v3, #I2C_PSC]
        ; Run at 100kbps for now. TODO - Add support for higher speeds!
        ; i.e. SCLL, SCLH = 12000000/(2*100000) = 60 (-7 for SCLL, -5 for SCLH)
        MOV     a4, #60-7
        STRH    a4, [v3, #I2C_SCLL]
        MOV     a4, #60-5
        STRH    a4, [v3, #I2C_SCLH]
        ; Program own address
        MOV     a4, #1
        STRH    a4, [v3, #I2C_OA0]
        ; Set FIFO thresholds
        LDRH    a4, [v3, #I2C_BUFSTAT]
        ADR     a3, fifo_thresholds
        AND     a4, a4, #&C000 ; Get FIFO size
        LDR     a4, [a3, a4, LSR #12]
        STRH    a4, [v3, #I2C_BUF]
        ; Enable auto idle, smart idle. Both clocks off when in idle mode?
        MOV     a4, #1+(2<<3)
        STRH    a4, [v3, #I2C_SYSC]
        ; Enable the controller
        MOV     a4, #1:SHL:15
        STRH    a4, [v3, #I2C_CON]
        ; Next!
        SUBS    v1, v1, #1
        ADD     v2, v2, #I2CBlockSize
        BNE     %BT10
        ; Wait for last controller to init fully
        MOV     a1, #10*1024 ; 10msec ish
        BL      HAL_CounterDelay
        ; Done
30
 [ I2CDebug
        DebugTX "I2C_Init complete"
 ]
        Pull    "v1-v3,pc"

fifo_thresholds
        DCD     &0606 ; 8 byte FIFO: Threshold at 7 bytes
        DCD     &0B0B ; 16 byte FIFO: Threshold at 12 bytes
        DCD     &1B1B ; 32 byte FIFO: Threshold at 28 bytes
        DCD     &3B3B ; 64 byte FIFO: Threshold at 60 bytes

HAL_IICBuses
        LDRB    a1, [sb, #BoardConfig_NumI2C]
        MOV     pc, lr

HAL_IICType
        ; todo - set the 'missing flags' alluded to in Kernel.Docs.HAL.MoreEnts? (multi-master & slave operation)
        LDRB    a2, [sb, #BoardConfig_NumI2C]
        CMP     a1, a2
        MOVHS   a1, #0
        LDRLO   a1, =IICFlag_HighLevel+IICFlag_Fast+IICFlag_Background+IICFlag_HighSpeed+(210:SHL:20)
        MOV     pc, lr

; HAL_IICDevice
; in:
;       r0 = irq_descriptor ptr
;       r1 = bus number
; out:
;       r0 filled in
; typedef struct irq_descriptor
; {
;     int device;
;     union {
;        struct {
;          unsigned char *addr;
;          int maskandpolarity;
;        } bit;
;        struct {
;          int (*forme)(void *handle);
;          void *handle;
;        } func;
;     } sub;
; } irq_descriptor;
;
HAL_IICDevice
        LDRB    a3, [sb, #BoardConfig_NumI2C]
        CMP     a2, a3
        MOVHS   a2, #-1
        ADDLO   a3, sb, #BoardConfig_HALI2CIRQ
        LDRLOB  a2, [a3, a2]
        MOV     a3, #0
        MOV     a4, #0
        STMIA   a1, {a2-a4}
        MOV     pc, lr

; HAL_IICTransfer
; in:
;      r0 = bus number
;      r1 = number of transfers
;      r2 = iic_transfer array ptr
; out:
;      r0 = IICStatus return code
; Transfer list format:
;      typedef struct iic_transfer
;      {
;        unsigned addr:8;
;        unsigned :22;
;        unsigned checksumonly:1;
;        unsigned nostart:1;
;        union
;        {   unsigned checksum;
;            void *data;
;        } d;
;        unsigned len;
;      } iic_transfer;

HAL_IICTransfer
        LDRB    a4, [sb, #BoardConfig_NumI2C]
        CMP     a1, a4
        MOVHS   a1, #IICStatus_Error
        MOVHS   pc, lr
        ; Quickly validate the transfer list
        ; We have several constraints:
        ; 1. Must have 1 or more iic_transfers
        ; 2. First transfer must not have nostart bit set
        ; 3. Between start bits (and between the last start bit and the end of  the list) there must be between 1 and 65536 bytes of data (but for the moment we do allow individual iic_transfers to be 0-length)
        CMP     a2, #0
        MOVLT   a1, #IICStatus_Error
        MOVLT   pc, lr
        LDR     a4, [a3]
        TST     a4, #1:SHL:31 ; First transfer has nostart set!
        MOVNE   a1, #IICStatus_Error
        MOVNE   pc, lr
        STMFD   sp!, {v1-v5,lr}
        ADD     a4, a3, a2, LSL #3
        ADD     a4, a4, a2, LSL #2
        MOV     v1, a3
30
        MOV     v5, #-1
10
        LDMIB   v1!, {v2-v4}    ; data ptr & length from current transfer, flags from next
        ADD     v5, v5, v3
        CMP     v1, a4
        BEQ     %FT20           ; Last transfer just read
        TST     v4, #1:SHL:31
        BNE     %BT10           ; Still more data in this transfer
20
        CMP     v5, #65536
        MOVHS   a1, #IICStatus_Error    ; Too much (or too little) data
        LDMHSIA sp! ,{v1-v5,pc}
        CMP     v1, a4
        BNE     %BT30
_IICTransfer_Video ; Entry point for HAL_VideoIICOp
 [ I2CDebug
        DebugTX "HAL_IICTransfer"
        DebugReg a1, "bus="
        DebugReg a2, "num="
        DebugReg a3, "iic_transfer="
 ]
        ADR     v5, I2C_Table
        MOV     v4, #I2CBlockSize
        MLA     v5, a1, v4, v5
        MRS     ip, CPSR
        ORR     a4, ip, #I32_bit
        MSR     CPSR_c, a4              ; disable interrupts for atomic claim
        LDR     a4, [v5, #I2C_XStart]
        TEQ     a4, #0                  ; in use already?
        STREQ   a3, [v5, #I2C_XStart]   ; if not, claim it
        MSR     CPSR_c, ip
        MOVNE   a1, #IICStatus_Busy     ; if it is, return "BUSY"
 [ I2CDebug
        BEQ     %FT10
        DebugReg a4, "BUSY: XStart="
        LDMFD   sp!, {v1-v5,pc}
10
        DebugTX "OK"
 |
        LDMNEFD sp!, {v1-v5,pc}
 ]
        SUB     a2, a2, #1              ; a2 = transfers - 1 (needed below)
        ADD     v1, a3, a2, LSL #3
        ADD     v1, v1, a2, LSL #2
        STR     v1, [v5, #I2C_XEnd]
        LDR     v4, [v5, #I2C_HW]
        ; Make sure controller is enabled, since we don't do any initialisation atm!
        LDRH    a4, [v4, #I2C_CON]
        TST     a4, #1:SHL:15
        LDREQH  v3, [v4, #I2C_STAT]
 [ I2CDebugError
        BNE     %FT10
        DebugTX "Controller not enabled!"
        B       unexpected_error
10
 |
        BEQ     unexpected_error
 ]
        MOV     v1, a3
start_transfer ; Start the transfer in v1
        ; a1-a4 free
        ; v1 = iic_transfer to start
        ; v2-v3 free
        ; v4 = I2C controller ptr
        ; v5 = I2C state ptr
 [ I2CDebug
        DebugReg v1, "start_transfer: "
 ]
        STR     v1, [v5, #I2C_XCurrent]
        MOV     lr, #0
        STR     lr, [v5, #I2C_XBytes]
        ; Get its info
        LDMIA   v1, {a1-a3}
 [ I2CDebug
        DebugReg a1, "addr="
        DebugReg a2, "data="
        DebugReg a3, "len ="
 ]
        ; If it's a 'checksum-only' read, clear the checksum to 0
        TST     a1, #1:SHL:30
        TSTNE   a1, #1
        MOVNE   a2, #0
        STRNE   a2, [v1, #4]
        ; Wait for the controller to be fully idle - if two iic_transfer lists are executed back-to-back then there's a chance the controller won't have yet sent the stop bit from the previous transfer. Thus we must wait here for the bit to be sent.
        MOV     v2, #100*1024 ; timeout - this should be more than adequate (with a CPU of 1GHz, there'd be 10K CPU clock cycles per 100kbps I2C clock cycle)
10
        LDRH    a4, [v4, #I2C_CON]
        TST     a4, #3 ; Check STT and STP (Although we're primarily interested in STP, the manual warns about bad things happening if STT is cleared)
        BEQ     %FT20
        SUBS    v2, v2, #1
        BNE     %BT10
 [ I2CDebug
        DebugReg a4, "ISC_CON timeout: "
 ]
        MOV     a1, #IICStatus_Busy
        MOV     v3, #0
        STR     v3, [v5, #I2C_XStart]
        LDMFD   sp!, {v1-v5,pc}
20
        ; Flush the RX/TX FIFOs
        ; The manual says we only need to do this on NACK errors, but to play it safe I'm going to do it at the start of each transfer
        LDRH    v2, [v4, #I2C_BUF]
        LDR     v3, =&4040
        ORR     v2, v2, v3
        STRH    v2, [v4, #I2C_BUF]
10
        LDRH    v2, [v4, #I2C_BUF]
        TST     v2, v3
        BNE     %BT10
        ; Configure controller
        ; 1. I2C_CON.MST, ISC_CON.TRX
        BIC     a4, a4, #&FF ; Clear STT, STP, XOA0-XOA3
        BIC     a4, a4, #&F00 ; Clear STB, MST, TRX, XSA
        ORR     a4, a4, #&400 ; Set MST (slave mode not supported ATM)
        TST     a1, #1
        ORREQ   a4, a4, #&200 ; Set transmitter mode if appropriate
        STRH    a4, [v4, #I2C_CON]
        ; Clear any pending interrupts, just in case?
        LDRH    v2, [v4, #I2C_STAT]
        STRH    v2, [v4, #I2C_STAT]
        ; 2. I2C_IE.XRDY_IE, I2C_IE.RRDY
        MOVEQ   v2, #&17 ; AL_IE, NACK_IE, ARDY_IE, XRDY_IE
        MOVNE   v2, #&0F ; AL_IE, NACK_IE, ARDY_IE, RRDY_IE
        ORREQ   v2, v2, #&4000 ; XDR_IE
        ORRNE   v2, v2, #&2000 ; RDR_IE
        STRH    v2, [v4, #I2C_IE]
        ; 3. Ignore DMA for now
        ; 4. I2C_SA, I2C_CNT
        MOV     v2, a1, LSR #1
        AND     v2, v2, #&7F
        STRH    v2, [v4, #I2C_SA]
        ; The I2C controller doesn't seem to like it if we don't send a start bit - it simply ignores I2C_CNT and goes straight to sending the stop bit (or sits there and does nothing if no stop bit was wanted).
        ; So to get around this we scan forward through the iic_transfer list and set I2C_CNT to the number of bytes to transmit before the next start/stop bit is required
        MOV     v2, v1
        LDR     ip, [v5, #I2C_XEnd]
10
        CMP     ip, v2
        ORREQ   a4, a4, #2 ; Last transfer in list; set stop bit
        BEQ     %FT20
        ADD     v2, v2, #12 ; Increment after compare, just in case some crazy person creates an iic_transfer that wraps from &FFF.... to &000...
        LDMIA   v2,{a1-a2,v3} ; Get transfer info
        TST     a1, #1:SHL:31
        ADDNE   a3, a3, v3 ; nostart is set; increment length and loop around
        BNE     %BT10
20
 [ I2CDebug
        DebugReg a3, "I2C_CNT="
 ]
        STRH    a3, [v4, #I2C_CNT]
        ; 5. Wait for I2C_STAT.BB to 0 (but only if this is the first iic_transfer of a sequence - otherwise we'll be stuck waiting to unlock a bus we already own)
        LDR     v2, [v5, #I2C_XStart]
        CMP     v1, v2 ; v1 = I2C_XCurrent from earlier
        BNE     %FT20
        MOV     v2, #50*1024 ; timeout
10
        LDRH    v3, [v4, #I2C_STAT]
        TST     v3, #1:SHL:12
        BEQ     %FT20
        SUBS    v2, v2, #1
        BNE     %BT10
 [ I2CDebug
        DebugReg v3, "BB timeout: I2C_STAT="
 ]
        MOV     a1, #IICStatus_Busy
        B       clear_and_return
20
        ; 6. configure I2C_CON.STT=1, I2C_CON.STP=0/1
        ORR     a4, a4, #1 ; Always send start bit
        ; If required, stop bit will already have been set by the I2C_CNT calculator
        STRH    a4, [v4, #I2C_CON]
        ; Now we just sit back and wait for the interrupts?
 [ I2CDebug
        DebugTX "Transfer started"
 ]
        MOV     a1, #IICStatus_InProgress
        LDMFD   sp!, {v1-v5,pc}

        ; For receive:
        ; 1. Use I2C_IE.RRDY_IE
        ; 2. Except we use I2C_IE.RDR_IE if the receive size doesn't match the RX FIFO threshold?
        ; For transmit:
        ; 1. Use I2C_IE.XRDY_IE
        ; 2. Except we use I2C_IE.XDR_IE if the length doesn't match the TX FIFO threshold?

; Return IICStatus state for transfer on bus r0
; Called on appropriate interrupt
HAL_IICMonitorTransfer
        ; Process the interrupts, according to figures 18-31/18-32 in spruf98b
        STMFD   sp!, {v1-v5,lr}
 [ I2CDebug
        DebugReg a1, "HAL_IICMonitorTransfer: bus "
 ]
        ADR     v5, I2C_Table
        MOV     v4, #I2CBlockSize
        MLA     v5, a1, v4, v5
        LDR     v4, [v5, #I2C_HW]
        LDR     a1, [v5, #I2C_XStart]
 [ I2CDebug
        DebugReg a1, "XStart="
 ]
        LDRH    v3, [v4, #I2C_STAT]
        TEQ     a1, #0 ; If no transfer, shut off all interrupts
        ASSERT  IICStatus_Completed=0
 [ I2CDebug
        BNE     %FT10
        DebugTX "No XStart!"
        TEQ     a1, #0 ; reset EQ condition
10
 ]
        STREQH  a1, [v4, #I2C_IE]
        STREQH  v3, [v4, #I2C_STAT]
        LDMEQFD sp!, {v1-v5,pc}
 [ I2CDebug
        DebugReg v3, "I2C_STAT="
 ]
        TST     v3, #2
        BNE     i2c_nack
        TST     v3, #1
        BNE     i2c_al
        TST     v3, #4
        BNE     i2c_ardy
        TST     v3, #1:SHL:14
        BNE     i2c_xdr
        TST     v3, #1:SHL:4
        BNE     i2c_xrdy
        TST     v3, #1:SHL:13
        BNE     i2c_rdr
        TST     v3, #1:SHL:3
        BNE     i2c_rrdy
        ; Did anything actually happen?
        BIC     v3, v3, #&1d00 ; Clear XUDF, ROVR, BB, BF - they're status bits and don't indicate anything we care about here
        CMP     v3, #0
        MOVEQ   a1, #IICStatus_InProgress ; If nothing interesting happened, claim everything is OK (required for polling-mode transfers, e.g. HAL_VideoIICOp)
        LDMEQFD sp!, {v1-v5,pc}
        ; Else bad stuff - unhandled interrupt
 [ I2CDebugError
        DebugReg v3, "Unhandled IRQ - "
 ]
unexpected_error
        MOV     a1, #IICStatus_Error
clear_and_return
 [ I2CDebug
        DebugReg a1, "clear_and_return: "
 ]
        STRH    v3, [v4, #I2C_STAT]
        MOV     v3, #0
        STR     v3, [v5, #I2C_XStart]
        LDMFD   sp!, {v1-v5,pc}

i2c_nack
        ; No ack was received - give up and return error
        MOV     a1, #IICStatus_NoACK
        B       clear_and_return

i2c_al
        ; Arbitration lost - restart the transfer list
        STRH    v3, [v4, #I2C_STAT]
        LDR     v1, [v5, #I2C_XStart]
        B       start_transfer

i2c_ardy
        ; Previous transfer has completed successfully; start a new one
        LDR     a3, [v5, #I2C_XCurrent]
        LDR     a2, [v5, #I2C_XBytes]
        LDR     ip, [a3, #8]
        CMP     a2, ip
 [ I2CDebugError
        BEQ     %FT10
        DebugTX "HW requested wrong byte count"
        DebugReg a2, "I2C_XBytes="
        DebugReg ip, "xfer len="
        B       unexpected_error
10
 |
        BNE     unexpected_error ; Hardware hasn't requested the full number of bytes
 ]
        LDR     a4, [v5, #I2C_XEnd]
        CMP     a3, a4
        MOVEQ   a1, #IICStatus_Completed
        BEQ     clear_and_return
        ; Skip any zero-length nostart transfers that follow this one
        ADD     v1, a3, #12
10
        LDMIA   v1, {a1-a3}
        TST     a1, #1:SHL:31
        STREQH  v3, [v4, #I2C_STAT]
        BEQ     start_transfer
        CMP     a3, #0
 [ I2CDebugError
        BEQ     %FT5
        DebugReg a3, "nostart transfer with nonzero length, length="
        B       unexpected_error
5
 |
        BNE     unexpected_error ; nostart transfer with nonzero length = hardware hasn't requested full number of bytes
 ]
        CMP     v1, a4
        MOVEQ   a1, #IICStatus_Completed
        BEQ     clear_and_return
        ADD     v1, v1, #12
        B       %BT10


i2c_xdr
        ; Transfer I2C_BUFSTAT[5:0] bytes
        LDRH    a4, [v4, #I2C_BUFSTAT]
        AND     a4, a4, #&3F
        B       send_bytes

i2c_xrdy
        ; Transfer I2C_BUF[5:0]+1 bytes
        LDRH    a4, [v4, #I2C_BUF]
        AND     a4, a4, #&3F
        ADD     a4, a4, #1
send_bytes
 [ I2CDebug
        DebugReg  a4, "send_bytes: "
 ]
        LDR     v2, [v5, #I2C_XCurrent]
        LDR     a3, [v5, #I2C_XBytes] ; Get bytes sent
        LDMIB   v2, {a2,ip} ; Get data ptr, transfer length
10
        ; Check if we need to advance to the next iic_transfer
        CMP     a3, ip
        BEQ     %FT20
        LDRB    v1, [a2,a3]
 [ I2CDebugData
        DebugRegByte v1
 ]
        ADD     a3, a3, #1
        SUBS    a4, a4, #1
        STRB    v1, [v4, #I2C_DATA]
        BNE     %BT10
 [ I2CDebugData
        DebugTX ""
 ]
        STR     a3, [v5, #I2C_XBytes]
        MOV     a1, #IICStatus_InProgress
        STRH    v3, [v4, #I2C_STAT]
        LDMIA   sp!, {v1-v5,pc}
20
        ; Advance to next iic_transfer
        LDR     a3, [v5, #I2C_XEnd]
        CMP     a3, v2
 [ I2CDebugError
        BNE     %FT5
        DebugTX "End of transfer list but hardware wants more"
        B       unexpected_error
5
 |
        BEQ     unexpected_error ; Hardware is asking for more data than we can give
 ]
        ADD     v2, v2, #12
        STR     v2, [v5, #I2C_XCurrent]
        MOV     a3, #0
        STR     a3, [v5, #I2C_XBytes]
        LDMIA   v2, {a1-a2,ip}
        TST     a1, #1:SHL:31
        BNE     %BT10
 [ I2CDebugError
        DebugTX "nostart reached but hardware wants more"
 ]
        B       unexpected_error ; Hardware is asking for more data than we can give

i2c_rdr
        ; Read I2C_BUFSTAT[13:8] bytes
        LDRH    a4, [v4, #I2C_BUFSTAT]
        MOV     a4, a4, LSR #8
        ANDS    a4, a4, #&3F ; ERRATA - sometimes RDR is set when there's no data. So, don't attempt to read from the empty buffer.
        BEQ     %FT15
        B       read_bytes

i2c_rrdy
        ; Read I2C_BUF[13:8]+1 bytes
        LDRH    a4, [v4, #I2C_BUF]
        MOV     a4, a4, LSR #8
        AND     a4, a4, #&3F
        ADD     a4, a4, #1
read_bytes
 [ I2CDebug
        DebugReg  a4, "read_bytes: "
 ]
        LDR     v2, [v5, #I2C_XCurrent]
        LDMIA   v2, {a1-a2,ip} ; Get checksum flag, data ptr/checksum, transfer length
        LDR     a3, [v5, #I2C_XBytes] ; Get bytes received
10
        ; Check if we need to advance to the next iic_transfer
        CMP     a3, ip
        BEQ     %FT20
        TST     a1, #1:SHL:30 ; Checksum mode?
        LDRB    v1, [v4, #I2C_DATA]
        ADDNE   a2, a2, v1 ; adjust checksum
        STREQB  v1, [a2, a3]
 [ I2CDebugData
        DebugRegByte    v1
 ]
        SUBS    a4, a4, #1
        ADD     a3, a3, #1
        BNE     %BT10
 [ I2CDebugData
        DebugTX ""
 ]
        STR     a2, [v2, #4] ; Update checksum
        STR     a3, [v5, #I2C_XBytes]
15
        MOV     a1, #IICStatus_InProgress
        STRH    v3, [v4, #I2C_STAT]
        LDMIA   sp!, {v1-v5,pc}
20
        ; Advance to next iic_transfer
        LDR     a3, [v5, #I2C_XEnd]
        CMP     a3, v2
 [ I2CDebugError
        BNE     %FT5
        DebugTX "End of transfer list but hardware received more data"
        B       unexpected_error
5
 |
        BEQ     unexpected_error ; Hardware is receiving more data than we want
 ]
        ADD     v2, v2, #12
        STR     v2, [v5, #I2C_XCurrent]
        MOV     a3, #0
        STR     a3, [v5, #I2C_XBytes]
        LDMIA   v2, {a1-a2,ip}
        TST     a1, #1:SHL:31
 [ I2CDebugError
        BNE     %FT5
        DebugTX "nostart reached but hardware received more data"
        B       unexpected_error
5
 |
        BEQ     unexpected_error ; Hardware is receiving more data than we want
 ]
        TST     a1, #1:SHL:30 ; Checksum mode?
        MOVNE   a2, #0 ; Start with zero checksum (as per start_transfer)
        B       %BT10


; int HAL_VideoIICOp(uint32_t op, uint8_t *buffer, uint32_t *size)
; in:
;      r0 = b0-15 offset within IIC device to start at (currently assumed 8 bit)
;           b16-23 base IICAddress
;           b24-31 zero
;      r1 = buffer to read from/write to
;      r2 = pointer to number of bytes to transfer
; returns:
;      r0 = IICStatus return code
;      size = bytes successfully transferred (prior to any error)

HAL_VideoIICOp
        ; Make sure we've got a valid IIC bus to use
        LDRB    a4, [sb, #BoardConfig_VideoI2C]
        CMP     a4, #255
        MOV     ip, #0
        STREQ   ip, [a3]
        MOVEQ   a1, #IICStatus_Error
        MOVEQ   pc, lr
        ; Check for input passed end
        UBFX    a4, a1, #0, #16
        CMP     a4, #256
        STRCS   ip, [a3]
        MOVCS   a1, #IICStatus_Completed
        MOVCS   pc, lr
        ; Clip request at end
        Push    "a1-a3,lr"
        LDR     a3, [a3]
        ADD     ip, a4, a3
        CMP     ip, #256
        RSBHI   a3, a4, #256

        ; Build a set of iic_transfer blocks and call RISCOS_IICOpV
        ; We construct two iic_transfer blocks
        ; - First block is a single byte write containing the start address (lower 8 bits of r0)
        ; - Second block is a read. r2 bytes written to r1.
        ; Block 2:
        UBFX    a1, a1, #16, #8 ; Extract base IICAddress
        Push    "a1-a3"         ; Push the block on the stack (a2 & a3 are already correct)
        ; Block 1:
        BIC     a1, a1, #1      ; Clear RnW of base address
        ADD     a2, sp, #12     ; sp+12 should point to the 8 bit offset
        MOV     a3, #1
        Push    "a1-a3"

        ; Now attempt to start the transfer
        LDRB    a2, [sb, #BoardConfig_VideoI2C]
        MOV     a2, a2, LSL #24
        ADD     a2, a2, #2
        MOV     a1, sp
        ; If HAL_Init isn't done yet, we can't use RISCOS_IICOpV
        LDR     a3, HALInitialised
        CMP     a3, #0
        BEQ     %FT10
        LDR     a3, OSentries+4*OS_IICOpV
        BLX     a3
        B       %FT20
10
        BL      IIC_DoOp_Poll
20
        ; In case of error, assume nothing got transferred at all
        CMP     a1, #IICStatus_Completed
        LDREQ   a4, [sp, #(3*4)+(2*4)]  ; Clipped block 2 request size
        MOVNE   a4, #0
        ADD     sp, sp, #24             ; Junk the iic_transfer blocks
        STR     a1, [sp, #0]            ; Propagate return code
        LDR     a3, [sp, #8]
        STR     a4, [a3]                ; Actual transfer size
        Pull    "a1-a3,pc"

IIC_DoOp_Poll
        ; IIC transfer function that performs a polling transfer, similar to HAL_VideoIICOp
        ; This allows us to do IIC transfers before RISC OS is fully initialised (e.g. from inside HAL_Init)
        ; Parameters are identical to RISCOS_IICOpV:
        ; r0 = iic_transfer array ptr
        ; r1 = bits 0-23: iic_transfer count
        ;      bits 24-31: bus number
        ; Returns IICStatus return code in R0 (0 success, anything else failure)
        Push    "v1,lr"
 [ {FALSE}
        ; If IRQs and IIC IRQ are enabled, panic
        Push    "a1-a4"
        MRS     a1, CPSR
        TST     a1, #I32_bit
        BNE     %FT10
        ADR     a1, BoardConfig_HALI2CIRQ
        LDRB    a1, [a1, a2, LSR #24]
        IMPORT  HAL_IRQDisable
        BL      HAL_IRQDisable
        CMP     a1, #0
        BEQ     %FT10
        DebugTX "Warning - IIC_DoOp_Poll called with IIC IRQ enabled!"
        B       .
10
        Pull    "a1-a4"
 ]
        MOV     a3, a1
        MOV     a1, a2, LSR #24
        BIC     a2, a2, #&ff000000
        MOV     v1, a1
        BL      HAL_IICTransfer
        ; Now just poll until we're done
10
        CMP     a1, #IICStatus_InProgress ; Done?
        Pull    "v1,pc", NE
        ADR     lr, %BT10
        MOV     a1, v1
        B       HAL_IICMonitorTransfer

        END