//------------------------------------------------------
// Generic IIC Code
// IIC.xje Revision: 1.16
// (c)2002-2009 XJTAG Limited
//
// Disclaimer: XJTAG makes no guarantees whatsoever
// about this code.  You use it at your own risk ...
// This code requires XJTAG version 1.4 or later.
//------------------------------------------------------
// Access is through the following functions : These are explained further below.
//   IIC_Write(first_address_to_write_to, num_of_addresses_to_write_to, data_to_write)(error);
//   IIC_Read(first_address_to_read_from, num_of_addresses_to_read_from)(data_read,error);
//   IIC_CheckPresent()(error);
//   IIC_FindDevice()(I2CAddress, error)
//   IIC_Write_Bin_File(filepointer,fileposition,Address,Length)(error)
//   IIC_Read_Bin_File(filepointer,fileposition,Address,Length)(error)
/*
// The following Predefined constants are required in the device file : 
// The values for these will need to be modified for the device you are testing
INT IIC_ADDRESS := 0xa0; //This is the IIC address of the device you are testing
CONST INT IIC_ADDRESS_BYTES := 1; //This is the number of address bytes
CONST INT IIC_WRITE_PAGE_SIZE := 1; //This is the number of continious bytes can be written
CONST INT IIC_READ_PAGE_SIZE := 1;  //This is the number of continious bytes can be read
// The following are required but should be automatically created by XJDeveloper in globals.xje
// CONST INT RESULT_FAIL := 1;
// CONST INT RESULT_PASS := 0;
// CONST INT DEBUG := FALSE;
*/
// The IIC pins must be called : SDA, SCL.
// NB Code assumes that we can't exceed the IIC clock speed.
//------------------------------------------------------

//------------------------------------------------------
// CONSTANTS
//------------------------------------------------------

CONST INT DATA_WIDTH  := 8;
CONST INT IIC_TIMEOUT := 10; // number of attempts to acknowledge device

CONST INT READ  :=1;
CONST INT WRITE :=0;

//Check that a device at the IIC address specified in the device file 
//returns and ACK
//------------------------------------------------------------------
IIC_CheckPresent()(INT Result)
//------------------------------------------------------------------
  INT ack, error,j,busy;

  Result := RESULT_FAIL;
  j := 0;
  DO WHILE j < IIC_TIMEOUT
    IIC_Start()(busy);
    IF (!busy) THEN 
      IIC_Transmit(IIC_ADDRESS | j[0])(ack); // Some devices only respond to one type of access so try both alternately.
    END;  
    IF (ack || busy) THEN
      IIC_Reset();
      j := j + 1;
    ELSE
      j := IIC_TIMEOUT;
    END;
  END;
  IF (ack = 0) THEN
    Result := RESULT_PASS;     
    FOR j := 1 TO DATA_WIDTH
      SET SCL := I;
      SET SCL := 0;
    END;
  END;
  IIC_Stop();
  IIC_Reset();
END;

//------------------------------------------------------------------
// Prints out the IIC address of all devices that acknowledge.
// Returns the last IIC address found, and error = 1 if I2C bus busy.
//------------------------------------------------------------------
IIC_FindDevice()(INT Result, INT Address)
//------------------------------------------------------------------ 
  INT i ,j,  ack, error,busy,z;
  
  Result := RESULT_FAIL;
  IIC_Stop();
  FOR i := 1 TO 127
    j := 0;
    DO WHILE j < IIC_TIMEOUT
      IIC_Start()(busy);
      IF (!busy) THEN 
        IIC_Transmit((i<<1) | j[0])(ack);
      END;  

      IF (ack || busy) THEN
            IIC_Reset();
            j := j + 1;
      ELSE
            j := IIC_TIMEOUT;
      END;
    END;
    IF (busy) THEN
       PRINT("IIC Bus not free, can't write address 0x",HEX(i<<1)," to device.\n");
       RETURN;
    END;
    IF (ack = 0 && !busy) THEN
      Address := i<<1;
      PRINT("IIC device found at address ", Address, " HEX 0x", HEX(Address), "\n");
      Result := RESULT_PASS;     
    END;
    IIC_Stop();
  END;
END;

//  The following code can be used to set IIC_ADDRESS when there are 
//  multiples of the same device in a setup.
//  IIC_ADDRESS needs to be set as a global instead of a global constant.
//  i.e. detele the CONST from it's definition.
//  Replace "Device_X" with the board specific device references.
/*
//------------------------------------------------------
SetIICAddr()()
//------------------------------------------------------
  SWITCH DEVICE_REF
    CASE "Device_1"
      IIC_ADDRESS := 0x30;
    END;
    CASE "Device_2"
      IIC_ADDRESS := 0x32;
    END;
    CASE "Device_3"
      IIC_ADDRESS := 0x98;
    END;
    CASE "Device_4"
      IIC_ADDRESS := 0x9C;
    END;
  END;
END;

*/

//------------------------------------------------------------------
// Transmits the first "Length" bytes of "Data" to 
// "Address","Address+1",... "Address + Length - 1".
// Example : I2C_Write(3,2,0xAABB)(result) :
// writes 0xBB to address 3 and 0xAA to address 4
IIC_Write( INT byteAddress , INT Length , INT Data )( INT result )
//------------------------------------------------------------------

  INT ack WIDTH 1;
  INT i :=0;
  result := RESULT_PASS;
  
  IIC_SetupAddress( byteAddress, WRITE )( result );
  IF result THEN RETURN; END;

  IF Length > 0 THEN
    DO
      IF DEBUG THEN PRINT("Writing to byte address 0x",HEX(byteAddress),"\n"); END;
      IIC_Transmit( Data[(((i+1)*DATA_WIDTH)-1)..(i*DATA_WIDTH)])( ack );
      IF(ack = 1) THEN
        PRINT("IIC_Write Error: Interface failed to acknowledge data.\n");
        RETURN;
      END;
      byteAddress := byteAddress + 1;
      i := i + 1; 
    WHILE( i != Length )    
      IF (( byteAddress & ( IIC_WRITE_PAGE_SIZE- 1)) = 0) THEN
          IIC_Stop();
        IIC_SetupAddress( byteAddress , WRITE )( result );
        IF result THEN RETURN; END;
      END;
    END;
  END;
  
  IIC_Stop();
  result := RESULT_PASS;
  
END;


//------------------------------------------------------------------
// Read from "Address","Address+1",... "Address + Length - 1". 
// Stores in Data. 
// Example : I2C_Read(3,2)(data, result) given 0xBB, 0xAA written 
// to 3 and 4 respectively, gives data = 0xAABB
IIC_Read( INT byteAddress , INT Length )( INT Data, INT result )
//------------------------------------------------------------------

  INT value;
  INT i :=0, end :=FALSE;

  result := RESULT_PASS;
  Data :=0;
  
  IIC_SetupAddress( byteAddress, READ )( result );
  IF result THEN RETURN; END;

  DO
    IF DEBUG THEN PRINT("Ready to read from byte address 0x",HEX(byteAddress),"\n"); END;
    byteAddress := byteAddress + 1;
    i := i + 1;
    end := FALSE;   
    
    // check for last read by length or end of page.
    IF ( ( i = Length ) | (( byteAddress & ( IIC_READ_PAGE_SIZE -1)) = 0) ) THEN
          end := TRUE;
    END;
    // Read from address.
    
    IIC_Receive( end )( value ) ; // if last byte don't ack 
    
    // put the return data in the correct place in the array
    Data[((i*DATA_WIDTH)-1)..((i-1)*DATA_WIDTH)] := value;
    
  WHILE( i != Length )  
    // If address at end of page continued access is no longer valid,
    // and have to set up byte address again.
    IF (( byteAddress & ( IIC_READ_PAGE_SIZE -1)) = 0) THEN
        IIC_Stop();
      IIC_SetupAddress( byteAddress, READ )( result );
      IF result THEN RETURN; END;
    END;
  END;
  
  IIC_Stop();

END;

//------------------------------------------------------------------
// Sets up an IIC address
IIC_SetupAddress( INT byteAddress , INT read)( INT result )
//------------------------------------------------------------------

  INT ack WIDTH 1;
  INT i :=0, busy, shift;
  INT IIC_ADDR := IIC_ADDRESS | (byteAddress>>(IIC_ADDRESS_BYTES * DATA_WIDTH))<<1 ;

  result := RESULT_FAIL;

  // Gain access to the device whose IIC address is "IIC_ADDRESS"
  // Tries at most IIC_TIMEOUT times.
  DO WHILE i < IIC_TIMEOUT
    IIC_Start()(busy);
    IF (!busy) THEN 
      IIC_Transmit(IIC_ADDR)(ack);
    END;  
   
    IF (ack || busy) THEN
      IIC_Stop();
      i := i + 1;
    ELSE
      i := IIC_TIMEOUT;
    END;
    
  END;
  
  IF (busy) THEN
    PRINT("IIC Bus not free, can't write address to device, ",DEVICE_REF,".\n");
    RETURN;
  END;
  
  IF (ack = 1) THEN
    PRINT("IIC Error: ",DEVICE_REF," failed to acknowledge device address.\n");
    RETURN;
  END;
  
  // Setup byte address for write/read access
  FOR i := 1 FOR IIC_ADDRESS_BYTES
    shift := ( (IIC_ADDRESS_BYTES - i) * DATA_WIDTH);   
    IIC_Transmit( byteAddress >> shift )( ack );
    IF(ack = 1) THEN
      PRINT("IIC Error: ",DEVICE_REF," failed to acknowledge address.\n");
      RETURN;
    END;
  END;
  
  IF (read = READ) THEN
   // Now terminate write operation and do a read.
    IIC_Start()(busy);
  
    // Transmit address to read from.
    IIC_Transmit( IIC_ADDR | READ )( ack ); // Bottom bit denotes read.
  
  END;
  
  result := RESULT_PASS;
  
END;

//------------------------------------------------------------------
// Transmit DATA_WIDTH bits and check for an ACK.
IIC_Transmit(INT byte)(INT ack)
//------------------------------------------------------------------

  INT i;

  FOR i := 1 FOR DATA_WIDTH
    IIC_WriteBit( byte[DATA_WIDTH - i] );
  END;

  IIC_ReadBit()(ack);

  IF DEBUG THEN PRINT("Wrote := 0x",HEX(byte),"\n"); END;

END;


//------------------------------------------------------------------
// Read DATA_WIDTH bits and send an ACK (or not).
IIC_Receive(INT ack)(INT byte WIDTH DATA_WIDTH)
//------------------------------------------------------------------

  INT i, bit WIDTH 1;

  // Clear the byte so that top bits don't become set.
  byte := 0;

  FOR i := 1 FOR DATA_WIDTH
    IIC_ReadBit()(bit);
    byte[DATA_WIDTH - i] := bit;
  END;

  IIC_WriteBit(ack)();

  IF DEBUG THEN PRINT("Read := 0x",HEX(byte),"\n"); END;

END;

//------------------------------------------------------------------
// I2C Reset clears bus to a known state resady for IIC start
IIC_Reset()()
//------------------------------------------------------------------
  INT i, d := 0;
  SET SDA := I; 

  DO 
    SET SCL := I;
    SET d := SDA;
    i := i + 1;
  WHILE (i < 10) && d =0 
    SET SCL := 0;
  END;
END;

//------------------------------------------------------------------
// I2C start condition is high to low transition on SDA while SCL is high.
IIC_Start()(INT error)
//------------------------------------------------------------------
 INT c, d ;

  error := FALSE;

  IF READABLE(SCL) THEN
    SET c := SCL, d := SDA;
    IF (~c || ~d) THEN
    // have second attempt incase rise time of SDA is very slow
      SET c := SCL, d := SDA; 
    END;

    IF (~c || ~d) THEN
      error := TRUE;
      RETURN;
    END;
  ELSE
    SET d := SDA, SCL := I;
    IF (~d) THEN
      // have second attempt incase rise time of SDA is very slow
      SET d := SDA; 
    END;
    IF (~d) THEN
      error := TRUE;
      RETURN;
    END;
  END;

  SET SDA := 0;
  SET SCL := 0; // Set clock low ready for first data bit.
  IF DEBUG THEN PRINT("Start.\n");END;
END;

//------------------------------------------------------------------
// I2C stop condition is low to high transition on SDA while SCL is high.
IIC_Stop()()
//------------------------------------------------------------------
  SET SDA := 0;
  SET SCL := I;
  SET SDA := I;
  IF DEBUG THEN PRINT("Stop.\n");END;
END;

//------------------------------------------------------------------
// Read one bit over IIC.
IIC_ReadBit()(INT bit)
//------------------------------------------------------------------
  SET SDA := I;
  SET SCL := I; 
  SET SCL := 0, bit := SDA;  // Get data at end of cycle.
  IF DEBUG THEN PRINT("Read ", bit, ".\n");END;
END;

//------------------------------------------------------------------
// Send one bit over IIC.
IIC_WriteBit(INT bit)()
//------------------------------------------------------------------
  IF (bit[0]) THEN
 //   PRINT("Setting input\n");
    SET SDA := I;
  ELSE
 //   PRINT("Setting low\n");
    SET SDA := 0;
  END;

  // Ensure that the data is stable during the high period.
  SET SCL := I;
  SET SCL := 0;
  IF DEBUG THEN PRINT("Wrote ", bit, ".\n");END;
END;

//------------------------------------------------------------------
IIC_Write_Bin_File(FILE fp, INT filepos, INT Address, INT Length)( INT result )
//------------------------------------------------------------------
  INT i, data, amount, filelength;
  
  result := RESULT_PASS;
  i := Length;
  FSEEK(fp,0,2); // seek to end of file
  FTELL(fp)(filelength); // find total file length
  filelength := (filelength / DATA_WIDTH) - filepos; // find length from filepos to end of file

  IF (Length > filelength) THEN
    i := filelength;
  END;
  
  FSEEK(fp,DATA_WIDTH * filepos,0);// go back to position in file
  DO 
    IF (i >= IIC_WRITE_PAGE_SIZE) THEN
      amount := IIC_WRITE_PAGE_SIZE;
    ELSE
      amount := i;
    END;
    
  FGETI(fp,DATA_WIDTH*amount)(data);
  
  IIC_Write( Address , amount , data )( result );
  IF result THEN RETURN; END;
  
  i := i - amount;
  Address := Address + amount;
  
  WHILE ( i > 0); 

  END;
  
END;

//------------------------------------------------------------------
IIC_Read_Bin_File(FILE fp, INT filepos, INT Address, INT Length)( INT result )
//------------------------------------------------------------------
  INT i,data, amount;
  
  result := RESULT_PASS;
  i := Length;
  
  FSEEK(fp,DATA_WIDTH * filepos,0);
  DO 
    IF (i >= IIC_READ_PAGE_SIZE) THEN
      amount := IIC_READ_PAGE_SIZE;
    ELSE
      amount := i;
    END;
  
  IIC_Read( Address , amount )( data, result );
  IF result THEN RETURN; END;
  
  FWRITE(fp,data[((DATA_WIDTH*amount)-1)..0]);
  i := i - amount;
  Address := Address + amount;
  
  WHILE ( i > 0); 

  END;
  
END;

//------------------------------------------------------
IIC_Verify_Bin_File(STRING ProgFileName)(INT Result)
//------------------------------------------------------
  INT ProgFileLength, ProgFileLengthBytes, ProgData, ReadData, i;
  FILE ProgFile, ReadFile;

  FOPEN(ProgFileName,"r")(ProgFile);

  FSEEK(ProgFile,0,2); // seek to end of file
  FTELL(ProgFile)(ProgFileLength); // find total file length
  IF DEBUG THEN
    PRINT("ProgFileLength := ",ProgFileLength,"\n");
  END;
  ProgFileLengthBytes := ProgFileLength / DATA_WIDTH; // find length from filepos to end of file

  FOPEN("temp.bin","w+")(ReadFile);

  IIC_Read_Bin_File(ReadFile, 0, 0, ProgFileLengthBytes)( Result );
  IF Result THEN RETURN; END;

  FSEEK(ProgFile,0,0);
  FSEEK(ReadFile,0,0);
  
  FGETI(ProgFile,ProgFileLength)(ProgData);
  FGETI(ReadFile,ProgFileLength)(ReadData);
  
  FOR i := 0 FOR ProgFileLengthBytes
    IF DEBUG THEN
      PRINT("PData := 0x",HEX(ProgData[(i*DATA_WIDTH)+7..i*DATA_WIDTH]),"\tRData := 0x",HEX(ReadData[(i*DATA_WIDTH)+7..i*DATA_WIDTH]),"\n");
    END;
    IF ProgData[(i*DATA_WIDTH)+7..i*DATA_WIDTH] != ReadData[(i*DATA_WIDTH)+7..i*DATA_WIDTH] THEN
      PRINT("Verify Failed at address 0x",HEX(i),": Expected := 0x",HEX(ProgData[(i*DATA_WIDTH)+7..i*DATA_WIDTH])," Read := 0x",HEX(ReadData[(i*DATA_WIDTH)+7..i*DATA_WIDTH]),"\n");
      FCLOSE(ReadFile);
      FCLOSE(ProgFile);
      Result := RESULT_FAIL;
      RETURN;
    END;
  END;

  FCLOSE(ReadFile);
  FCLOSE(ProgFile);
    
END;