#include "common.inc"

    global  I2CInit
    global  I2CWrite
    global  I2CWriteRead
    global  I2CData
    global  I2CAddress

    udata

I2CAddress  res 1   ; I2C Slave Addresss
I2CData     res 1   ; I2C read or write data
SaveStatus  res 1   ; Tmp status save

    code

    ; SSPIF will be used, but we dont enable interrupts for I2C
    ; 
    ; MSSP Registers
    ;
    ; SSPCON  Control 
    ; SSPCON2 Control 
    ; SSPSTAT Status 
    ; SSPBUF  Serial Receive/Transmit Buffer 
    ; SSPADD  Address in slave mode. When SSP is configured in Master mode
    ;         the lower 7 bits of SSPADD act as baud generator reload value
    ;
    ; Shift register SSPSR is not accessable
    ; Return with status in W. 0 means success

I2CInit:
    swapf   STATUS,W
    BANKSEL SaveStatus
    movwf   SaveStatus

    BANKSEL TRISC

    ; Port4,3 input, already done in top.

    ; bsf     TRISC,3
    ; bsf     TRISC,4

    BANKSEL SSPADD
    movlw   .10         ; 7 bit: 100 KHz at 4 MHz clock
    movwf   SSPADD

    BANKSEL SSPSTAT
    movlw   b'10000000' ; Disable slew rate control, Transmit Idle->Active
    movwf   SSPSTAT

    BANKSEL SSPCON
    movlw b'00101000'   ;  SDA,SCL enable, Clock Idle High, I2C master
    movwf SSPCON

    BANKSEL SSPCON2
    movlw b'00000000'
    movwf SSPCON2       ; Continuous reception.

    BANKSEL SaveStatus
    swapf   SaveStatus,W
    movwf   STATUS
    movlw   0
    return 

I2CAckError:

    call    Stop

    ; 12. Interrupt is generated once the Stop condition is
    ; complete.

    call    Wait   
    retlw   1  
    

I2CWrite:

    ; Write a single byte I2CData to the seven bit I2CAddress[6:0] slave.
    ; Return with 0 when there is no error.

    swapf   STATUS,W
    BANKSEL SaveStatus
    movwf   SaveStatus

    ; A typical transmit sequence would go as follows:

    ; 1. The user generates a Start condition by setting
    ; the Start Enable bit, SEN (SSPCON2<0>).

    call    Start

    ; 2. SSPIF is set. The MSSP module will wait the
    ; required Start time before any other operation
    ; takes place.

    call    Wait

    ; 3. The user loads the SSPBUF with the slave
    ; address to transmit. (7 bit address + 0 bit)

    BANKSEL I2CAddress
    bcf     STATUS,C     
    rlf     I2CAddress,W
    BANKSEL SSPBUF
    movwf   SSPBUF

    ; 4. Address is shifted out the SDA pin until all 8 bits
    ; are transmitted.
    ; 5. The MSSP module shifts in the ACK bit from the
    ; slave device and writes its value into the
    ; SSPCON2 register (SSPCON2<6>).
    ; 6. The MSSP module generates an interrupt at the
    ; end of the ninth clock cycle by setting the SSPIF
    ; bit.

    call    Wait

    BANKSEL SSPCON2
    btfsc   SSPCON2,ACKSTAT
    goto    I2CAckError

    ; 7. The user loads the SSPBUF with eight bits of
    ; data.

    BANKSEL I2CData
    movf    I2CData,W
    BANKSEL SSPBUF
    movwf   SSPBUF

    ; 9. The MSSP module shifts in the ACK bit from the
    ; slave device and writes its value into the
    ; SSPCON2 register (SSPCON2<6>).
    ; 10. The MSSP module generates an interrupt at the
    ; end of the ninth clock cycle by setting the SSPIF
    ; bit.

    call    Wait

    BANKSEL SSPCON2
    btfsc   SSPCON2,ACKSTAT
    goto    I2CAckError

    ; 11. The user generates a Stop condition by setting
    ; the Stop Enable bit, PEN (SSPCON2<2>).

    call    Stop

    ; 12. Interrupt is generated once the Stop condition is
    ; complete.

    call    Wait
    
    BANKSEL SaveStatus
    swapf   SaveStatus,W
    movwf   STATUS
    retlw   0

I2CWriteRead:

    ; Write a single byte I2CData to the seven bit I2CAddress[6:0] slave.
    ; Read back a single byte.
    ; Return with 0 when there is no error.

    swapf   STATUS,W
    BANKSEL SaveStatus
    movwf   SaveStatus

    ; 1. The user generates a Start condition by setting
    ; the Start Enable bit, SEN (SSPCON2<0>).

    call    Start

    ; 2. SSPIF is set. The MSSP module will wait the
    ; required Start time before any other operation
    ; takes place.

    call    Wait

    ; 3. The user loads the SSPBUF with the slave
    ; address to transmit. (7 bit address + 0 bit)

    BANKSEL I2CAddress
    bcf     STATUS,C     
    rlf     I2CAddress,W
    BANKSEL SSPBUF
    movwf   SSPBUF

    ; 4. Address is shifted out the SDA pin until all 8 bits
    ; are transmitted.
    ; 5. The MSSP module shifts in the ACK bit from the
    ; slave device and writes its value into the
    ; SSPCON2 register (SSPCON2<6>).
    ; 6. The MSSP module generates an interrupt at the
    ; end of the ninth clock cycle by setting the SSPIF
    ; bit.

    call    Wait

    BANKSEL SSPCON2
    btfsc   SSPCON2,ACKSTAT
    goto    I2CAckError

    ; 7. The user loads the SSPBUF with eight bits of
    ; data.

    BANKSEL I2CData
    movf    I2CData,W
    BANKSEL SSPBUF
    movwf   SSPBUF

    ; 9. The MSSP module shifts in the ACK bit from the
    ; slave device and writes its value into the
    ; SSPCON2 register (SSPCON2<6>).
    ; 10. The MSSP module generates an interrupt at the
    ; end of the ninth clock cycle by setting the SSPIF
    ; bit.

    call    Wait

    ; 11. The user generates a Restart condition by setting
    ; the Restart Enable bit, RSEN (SSPCON2<2>).

    call    Restart


    ; 12. SSPIF is set. The MSSP module will wait the
    ; required Start time before any other operation
    ; takes place.

    call    Wait

    ; 13. The user loads the SSPBUF with the slave
    ; address to transmit. (7 bit address + 0 bit)

    BANKSEL I2CAddress
    bsf     STATUS,C     ; Reading from device
    rlf     I2CAddress,W
    BANKSEL SSPBUF
    movwf   SSPBUF

    ; 14. Address is shifted out the SDA pin until all 8 bits
    ; are transmitted.
    ; 15. The MSSP module shifts in the ACK bit from the
    ; slave device and writes its value into the
    ; SSPCON2 register (SSPCON2<6>).
    ; 16. The MSSP module generates an interrupt at the
    ; end of the ninth clock cycle by setting the SSPIF
    ; bit.

    call    Wait

    BANKSEL SSPCON2
    btfsc   SSPCON2,ACKSTAT
    goto    I2CAckError

    ; 17. Set the receive enable bit
    ; 18. Address is shifted into the SDA pin until all 8 bits
    ; are transmitted.

    call    Receive

    ; 19. The MSSP module generates an interrupt at the
    ; end of the ninth clock cycle by setting the SSPIF
    ; bit.

    call    Wait

    ; 20. The user copies the data from SSPBUF 

    BANKSEL SSPBUF
    movf    SSPBUF,W
    BANKSEL I2CData
    movwf   I2CData

    call    Nack        ; Indicate unwillingness to read more data

    call    Wait        ; Wait for interrupt flag

    call    Stop        ; Initiate stop sequence
    call    Wait        ; Wait for interrupt flag

    BANKSEL SaveStatus
    swapf   SaveStatus,W
    movwf   STATUS
    retlw   0

Start:
    BANKSEL SSPIF
    bcf     PIR1,SSPIF  ; Clear interrupt flag

    BANKSEL SSPCON2
    bsf     SSPCON2,SEN ; Start transmission
    return

Stop:
    BANKSEL SSPCON2
    bsf     SSPCON2,PEN ; Stop transmission
    return

Restart:
    BANKSEL SSPIF
    bcf     PIR1,SSPIF  ; Clear interrupt flag

    BANKSEL SSPCON2
    bsf     SSPCON2,RSEN ; Restart transmission
    return

Ack:
    BANKSEL SSPCON2
    bcf     SSPCON2,ACKDT ; Set ack
    bsf     SSPCON2,ACKEN ; Initiate ack sequence
    return

Nack:
    BANKSEL SSPCON2
    bsf     SSPCON2,ACKDT ; Clear ack
    bsf     SSPCON2,ACKEN ; Initiate ack sequence
    return

Receive:
    BANKSEL SSPCON2
    bsf     SSPCON2,RCEN ; Set the receive enable bit.
    return

Wait:
    BANKSEL PIR1

WaitLoop:
    ;
    ; btfss SSPSTAT,BF has the same effect
    ;
    btfss   PIR1,SSPIF ; Check interrupt flag. It is set even if is not enabled
    goto    WaitLoop
    bcf     PIR1,SSPIF ; Clear flag
    return

    end
