/********************************************************************
*  FUNCTION MODULE:           i2c_if.c
*
*  I2C Serial Data Interface Module for Silabs C8051F133
*
*********************************************************************
*
*  DESCRIPTION:
*
*  Serial I2C/SMBus bus access.
*
*	This module is believed to be able to talk to any I2C compliant device.
*	It has been tested with:
*		- X64645 EEPROM (obsolete in 2003)
*		- M24C64 EEPROM
*		- MAX518 DAC
*		- MAX5821 DAC
*		- DS1629 Real Time Clock
*		- MAX1609 parallel I/O extender
*
*	Note: this module includes two versions:
*	1) a version using hardware transceiver and
*	2) a version using bit-banging.
*
*	Define appropriate compilation option I2C_HARDWARE or I2C_BIT_BANGED
*
**********************************************************************/


#define  I2C_HARDWARE
//#define I2C_BIT_BANGED

/*
*
*  REVISION LOG (don't use TABs for rev log):
*
*  Date     Name              Version  Reason
*  -------  ----------------  -------  -------------------------------
*  08-31-05 E.Burzic/D.Juges  0.0.1    Initial design from Silabs and Internet
*  09-30-05 E.Burzic/D.Juges  0.2.1    bit-banged or hardware options
*********************************************************************/

/*--------------------------------------------------------------------*
*  INCLUDE FILES
*--------------------------------------------------------------------*/
#include "f120.h"
#include "comdefs.h"

#include "i2c_if.h"

/*---------------------------------------------------------------------*
* COMMON CONSTANTS
*----------------------------------------------------------------------*/
#define ESMB0		2		// SMB interrupt enable bit in sfr EIE1


/*---------------------------------------------------------------------*
* common public variables (used by either version)
*---------------------------------------------------------------------*/
unsigned char xdata _i2c_error;
unsigned char xdata smb_status;

/*---------------------------------------------------------------------*
* COMMON FUNCTION: I2CCheckError()  (used by either version)
*
*	Returns 0 if no error during previous I2C function, a non-zero
*	error code otherwise.
*---------------------------------------------------------------------*/
unsigned char I2CCheckError( void ){

	return( _i2c_error );

} /* end I2CCheckError() */

/*---------------------------------------------------------------------*
* FUNCTION: I2CClearError()
*----------------------------------------------------------------------*/
/*void I2CClearError( void ){

	_i2c_error = 0;

} /* end I2CClearError() */



#ifdef I2C_HARDWARE

/*---------------------------------------------------------------------*
*	CONSTANTS
*---------------------------------------------------------------------*/

#define I2C_INIT    0x40	// enable SMB transceiver

#define AA_BIT      0x04
#define STO_BIT     0x10
#define STA_BIT     0x20

/*--------------------------------------------------------------------*
*   SMBus states for Master Tx or Rx:
*
*   MT = Master Transmitter (Tx)
*   MR = Master Receiver (Rx)
*   ADD = Address transmitted
*   DB = Data Byte transmitted/received
*----------------------------------------------------------------------*/
#define  SMB_BUS_ERROR  0x00        // (all modes) BUS ERROR
#define  SMB_START      0x08        // (MT & MR) START transmitted
#define  SMB_RP_START   0x10        // (MT & MR) repeated START
#define  SMB_MTADDACK   0x18        // (MT) Slave address + W transmitted;
                                    //  ACK received
#define  SMB_MTADDNACK  0x20        // (MT) Slave address + W transmitted;
                                    //  NACK received
#define  SMB_MTDBACK    0x28        // (MT) data byte transmitted; ACK rec'vd
#define  SMB_MTDBNACK   0x30        // (MT) data byte transmitted; NACK rec'vd
#define  SMB_MTARBLOST  0x38        // (MT) arbitration lost
#define  SMB_MRADDACK   0x40        // (MR) Slave address + R transmitted;
                                    //  ACK received
#define  SMB_MRADDNACK  0x48        // (MR) Slave address + R transmitted;
                                    //  NACK received
#define  SMB_MRDBACK    0x50        // (MR) data byte rec'vd; ACK transmitted
#define  SMB_MRDBNACK   0x58        // (MR) data byte rec'vd; NACK transmitted


#define BUF_LEN				14
unsigned char xdata buffer[BUF_LEN];	// ***TEST
unsigned char data smb_buf_ndx;			// ***TEST

/* ----- local function prototypes ----- */
void clearbuffer( void );

/*=====================================================================*
*   GENERIC I2C FUNCTIONS
*=====================================================================*/

/*---------------------------------------------------------------------*
*  I2CSendAddr()
*
*   addr is an address between 0 and 255
*   rd is either WRITE or READ
*
*   - initiates START condition
*   - send device select address, with appropriate R/W bit as provided in rd
*   - wait for acknowledge from device
*   - if timeout before acknlowledge is received, set error flag
*---------------------------------------------------------------------*/
void I2CSendAddr( unsigned char addr, unsigned char rd ){

	int xdata i=0;

	EA = FALSE;                     // Disable global interrupt.

	if( ! rd ){
		clearbuffer();
		SMB0CN = I2C_INIT;               /* SIO1 enable, with bit frequency set.         */
	}

	_i2c_error  = 0;                /* Clear error flag.                            */
	smb_status = 0;

	SMB0CN = I2C_INIT | STA_BIT;     /* SIO1 send START.                             */

	do{
		i++;
	}while( (! SI) && i>0 );        /* Hold here until Serial Interrupt FLag or     */

//	while( SI == 0 );
                                    /* counter rollover.                            */
	if( SMB0STA != SMB_START && SMB0STA != SMB_MTDBACK &&
		SMB0STA != SMB_RP_START ){
		smb_status= SMB0STA;
		_i2c_error = 0x01;				// START not transmitted?
		I2CSendStop();
		EA = TRUE;
		return;
	}

	if( rd )                        /* I2C address fixup.                           */
		addr++;

	SMB0DAT = addr;                  /* Load address into Data Register  (which      */
	SMB0CN = I2C_INIT;               /* intitiates transfer), SMB enabled            */

	i = 0;
	do{
		i++;
	}while( (! SI) && (i) );        // Hold here until Serial Interrupt FLag or
                                    // counter rollover.

	if( SMB0STA == SMB_MTADDNACK ) 	// SLA + (W || R) has been transmitted,
	 											// NOT ACK has been received
		_i2c_error |= 0x02;         // set error #2

	if( SMB0STA == SMB_MRADDNACK )
		_i2c_error |= 0x04;

	EA = TRUE;                      // Enable global interrupt.

} /* end I2CSendAddress() */

/*---------------------------------------------------------------------*
*  I2CSendByte()
*
*   Send Byte and wait for acknowledge
*   Set error flag if timeout before acknowledge received.
*---------------------------------------------------------------------*/
void I2CSendByte( unsigned char byte_data ){

	int xdata i=0;

	EA = FALSE;

	SMB0DAT = byte_data;
	SMB0CN = I2C_INIT;          // Start Tx

	do{
		i++;
	}while( (! SI) && i>0 );   // wait for Tx done or timeout

	if( SMB0STA != SMB_MTDBACK ) // No ACK received after sending data?
		_i2c_error |= 0x08;

	EA = TRUE;

} /* end I2CSendByte() */

/*---------------------------------------------------------------------*
*  I2CGetByte()
*
*   Receive Byte and acknowledge
*---------------------------------------------------------------------*/
unsigned char I2CGetByte( void ){

	int xdata i = 0;

	EA = FALSE;                     // Disable global interrupt

	SMB0CN = I2C_INIT | AA_BIT;  /* SIO1 Enable, with ACK.   */

	do{
		i++;
	}while( (! SI) && i>0 );

	if( !i )                        /* SI never turned on?      */
		_i2c_error |= 0x10;         /*  Yes, set error #4.      */

	EA = TRUE;                      /* Enable global interrupt. */

	return( SMB0DAT );

} /* end I2CGetByte() */

/*---------------------------------------------------------------------*
* FUNCTION: I2CGetLastByte()
*
*   Receive byte and NAK
*----------------------------------------------------------------------*/
unsigned char I2CGetLastByte( void ){

	int xdata i = 0;

	EA = FALSE;

	SMB0CN = I2C_INIT;           /* SIO1 Enable, no ACK.     */

	do{
		i++;
	}while( (! SI) && i>0 );

	if( !i )                        /* SI never turned on?      */
		_i2c_error &= 0x20;         /*  Yes, set error #4.      */

	EA = TRUE;                      /* Enable global interrupt. */

	return( SMB0DAT );


} // end I2CGetLastByte()


/*---------------------------------------------------------------------*
*  I2CSendStop()
*---------------------------------------------------------------------*/
void I2CSendStop( void ){

	char xdata i = 0;

	SMB0CN = I2C_INIT | STO_BIT; /* SIO1 Enable,  generate STOP. */
	do{
		i++;
	}while( i>0 );	// wait a little
	SMB0CN = 0;		// reset transceiver

} /* end I2CSendStop() */


/*---------------------------------------------------------------------*
* I2C_Init()
*----------------------------------------------------------------------*/
void I2C_Init( void ){

	SMB0CN = I2C_INIT;			// enable SMBus transceiver
	SMB0CR = -60;				// 100 kHz
	EIE1 &= ~ESMB0;				// disable SMB interrupts
	XBR0 |= 0x01;				// enable SMBus crossbar

//	SMB0CN   = I2C_INIT;     /* SIO1 enable, with bit frequency set.     */

} /* end I2C_Init() */

/*-----------------------------------------------------------------------------------*
* FUNCTION: clearbuffer)
*------------------------------------------------------------------------------------*/
void clearbuffer( void ){

	char i;

	for(i=0;i<BUF_LEN;i++)
		buffer[i] = 0;
	smb_buf_ndx = 0;

} /* end clearbuffer() */

#endif // I2C_HARDWARE

#ifdef I2C_BIT_BANGED

/*---------------------------------------------------------------------*
* Ports
*
* Note: make sure these bits are set as Open Collector (default) outputs
*---------------------------------------------------------------------*/
sbit SCL  =     P0_3;    // I2C Clock
sbit SDA  =     P0_2;    // I2C Data


/*---------------------------------------------------------------------*
* Error Codes (_i2c_error)
*	0	No error
*	1	no acknowledge from Slave
*	2	no address ack
*	4	no data ack
*----------------------------------------------------------------------*/


/*---------------------------------------------------------------------*
* Private Functions Prototypes
*----------------------------------------------------------------------*/
void i2c_delay( void );                 // Wait for 5µs (for 100kHz operation)
void i2c_start( void );                 // Geneate I2C START condition
void i2c_stop( void );                  // Geneate I2C STOP condition
void i2c_ack( void );                   // Geneate I2C MASTER ACK
void i2c_nack( void );                  // Geneate I2C MASTER NACK
unsigned char i2c_write( unsigned char value ); 	// Write a character via I2C
unsigned char i2c_check( void );             // Check an ACK from SLAVE
unsigned char i2c_read( void );              // Read a character via I2C

// END Private Function Prototypes ======================================


/* ===== code ===== */

/*---------------------------------------------------------------------*
*  I2CSendAddr()
*
*	addr is an even number between 0 and 254
*	rd is either WRITE (0) or READ (1)
*
*	- initiates START condition
*	- send device select address, with appropriate R/W bit as provided in rd
*	- wait for acknowledge from device
*	- if timeout before acknlowledge is received, set error flag
*---------------------------------------------------------------------*/
void I2CSendAddr( unsigned char addr, unsigned char rd ){

	_i2c_error = 0;
	i2c_start();
	if( i2c_write( addr | rd ) != 0 )
		_i2c_error = 0x02;

} /* end I2CSendAddr() */

/*---------------------------------------------------------------------*
*  I2CSendByte()
*
*	Send Byte and wait for acknowledge
*	Set error flag if timeout before acknowledge received.
*---------------------------------------------------------------------*/
void I2CSendByte( unsigned char byte_data ){

	if( i2c_write( byte_data ))
		_i2c_error |= 0x04;

} /* end I2CSendByte() */


/*---------------------------------------------------------------------*
* FUNCTION: I2CGetByte()
*
*	Get one byte and acknowledge
*----------------------------------------------------------------------*/
unsigned char I2CGetByte( void ){

	unsigned char result;

	result = i2c_read();
	i2c_ack();
	i2c_stop();

	return( result );

} /* end I2CGetByte() */

/*---------------------------------------------------------------------*
* FUNCTION: I2CGetLastByte()
*
*	Get one byte and do not acknowledge (this stops transfer from
*	the slave)
*----------------------------------------------------------------------*/
unsigned char I2CGetLastByte( void ){

	unsigned char result;

	result = i2c_read();
	i2c_nack();
	i2c_stop();

	return( result );

} /* end I2CGetLastByte() */

/*---------------------------------------------------------------------*
*  I2CSendStop()
*---------------------------------------------------------------------*/
void I2CSendStop( void ){

	i2c_stop();

} /* end I2CSendStop() */


/*---------------------------------------------------------------------*
* FUNCTION: I2C_Init()
*----------------------------------------------------------------------*/
void I2C_Init( void ){

	XBR0 &= ~0x01;				// disable SMBus from crossbar
	EIE1 &= ~ESMB0;				// disable SMB interrupts (to be sure)
	ENSMB = 0;					// disable SMBus transceiver

} /* end I2C_Init() */

/********************************************************************/
/* Function    : i2c_delay()                                    SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine waits for about 5uS.               */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : none                                               */
/*------------------------------------------------------------------*/
/* Returnvalue : none                                               */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*               11/03    Changed for MSC1210Y5 (D. Juges)          */
/*               09/05    Changed for Silabs C8051F133 (D. Juges)   */
/********************************************************************/
void i2c_delay( void ){

	uchar data i;

	EA = 0;    // Disable all interrupts for a accurate timing
	for( i=0; i<20; i++ )
		;
	EA = 1;    // Enable all interrupts
	return;

} /* end i2c_delay() */


/********************************************************************/
/* Function    : i2c_start()                                    SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine generates a I2C START condition.   */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : none                                               */
/*------------------------------------------------------------------*/
/* Returnvalue : none                                               */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*                                                                  */
/********************************************************************/
void i2c_start( void ){

	EA = 0;        	// Disable all interrupts for accurate timing

	// Force defined state of SCL and SDA
	SCL = 1;       	// Release SCL
	SDA = 1;       	// Release SDA
	i2c_delay();   	// Force 5µs-delay

	// Generate START condition
	SDA = 0;
	i2c_delay();   	// Force 5µs-delay
	SCL = 0;
	i2c_delay();
	EA = 1;        	// Enable all interrupts

	// I2C Busy

	return;

} /* end i2c_start() */


/********************************************************************/
/* Function    : i2c_stop()                                     SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine generates a I2C STOP condition.    */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : none                                               */
/*------------------------------------------------------------------*/
/* Returnvalue : none                                               */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*                                                                  */
/********************************************************************/
void i2c_stop( void ){

	EA = 0;        // Disable all interrupts for a accurate timing

	// Generate STOP condition
	SDA = 0;
	i2c_delay();   // Force 5µs-delay
	SCL = 1;       // Release SCL
	i2c_delay();   // Force 5µs-delay
	SDA = 1;       // Release SDA

	EA = 1;        // Enable all interrupts

	// I2C Idle

	return;

} /* i2c_stop() */


/********************************************************************/
/* Function    : i2c_ack()                                      SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine generates a I2C MASTER ACK.        */
/*               MASTER clock synchronisation (needed to handle     */
/*               SLAVE clock streching) is supported.               */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : none                                               */
/*------------------------------------------------------------------*/
/* Returnvalue : none                                               */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*                                                                  */
/********************************************************************/
void i2c_ack( void ){

	EA = 0;            		// Disable all interrupts for a accurate timing

	// Generate ACK
	SDA = 0;
	SCL = 1;           		// Release SCL
	while( SCL == 0 )
		;   				// Synchronize clock
	i2c_delay();       		// Force 5µs-delay
	SCL = 0;           		// Force a clock cycle

	EA = 1;            		// Enable all interrupts

	return;

} /* end i2c_ack() */


/********************************************************************/
/* Function    : i2c_nack()                                     SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine generates a I2C MASTER NACK.       */
/*               MASTER clock synchronisation (needed to handle     */
/*               SLAVE clock streching) is supported.               */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : none                                               */
/*------------------------------------------------------------------*/
/* Returnvalue : none                                               */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*                                                                  */
/********************************************************************/
void i2c_nack( void ){

	EA = 0;            		// Disable all interrupts for a accurate timing

	// Generate NACK
	SDA = 1;           		// Release SDA
	SCL = 1;           		// Release SCL
	while( SCL == 0 )
		;   				// Synchronize clock
	i2c_delay();       		// Force 5µs-delay
	SCL = 0;           		// Force a clock cycle

	EA = 1;            		// Enable all interrupts

	return;

} /* end i2c_nack() */


/********************************************************************/
/* Function    : i2c_check()                                    SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine checkes an I2C SLAVE ACK.          */
/*               MASTER clock synchronisation (needed to handle     */
/*               SLAVE clock streching) is supported.               */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : none                                               */
/*------------------------------------------------------------------*/
/* Returnvalue : unsigned char -> 0 : acknowledge from Slave        */
/*                                1 : no acknowledge from Slave     */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*                                                                  */
/********************************************************************/
unsigned char i2c_check( void ){

	EA = 0;					// Disable all interrupts for accurate timing

	SDA = 1;           		// Release SDA
	SCL = 1;           		// Release SCL
	while( SCL == 0 )
		;   				// Synchronize clock
	i2c_delay();       		// Force 5µs-delay
	if( SDA ){           	// SDA is high
		SCL = 0;          	// Force a clock cycle
		_i2c_error |= 1;
		return( 1 );      	// No acknowledge from Slave
	}
	SCL = 0;           		// Force a clock cycle
	i2c_delay();       		// Force 5µs-delay
	EA = 1;            		// Enable all interrupts

	return( 0 );         	// Acknowledge from Slave

} /* end i2c_check() */


/********************************************************************/
/* Function    : i2c_write(value)                               SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine writes a character to I2C.         */
/*               MASTER clock synchronisation (needed to handle     */
/*               SLAVE clock streching) is supported.               */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : unsigned char value -> data to I2C device          */
/*------------------------------------------------------------------*/
/* Returnvalue : unsigned char       -> 0 : Acknowledge received    */
/*                                      1 : No acknowledge received */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*                                                                  */
/********************************************************************/
unsigned char i2c_write( unsigned char value ){

	unsigned char idata c;  			// Bitcounter

	EA = 0;						// Disable all interrupts for a accurate timing
	for( c = 8; c > 0; c-- ){
		if( value & 0x80 )
			SDA = 1;
		else
			SDA = 0;
		SCL = 1;				// Release SCL
		SCL = 1;				// make it a little longer
		SCL = 1;
		while( SCL == 0 )
			;                	// Synchronize clock
		i2c_delay();			// Force 5µs-delay
		SCL = 0;				// Force a clock cycle
		i2c_delay();
		value <<= 1;			// Prepare next bit for transmission
	}

	EA = 1;						// Enable all interrupts

	// Generate a 9th clock cycle and check ACK from SLAVE
	// Return the result
	return( i2c_check() );

} /* end i2c_write() */


/********************************************************************/
/* Function    : i2c_read()                                     SUB */
/*------------------------------------------------------------------*/
/* Description : This subroutine reads a character from I2C.        */
/*               MASTER clock synchronisation (needed to handle     */
/*               SLAVE clock streching) is supported.               */
/*               (Low-level I2C routine)                            */
/*------------------------------------------------------------------*/
/* Author      : Thorsten Godau  NT8                                */
/*               (Based upon U24C08 EEPROM programming with C51 by  */
/*               Dimitry S. Obukhov dso@usa.net in 1996/97)         */
/*------------------------------------------------------------------*/
/* Input       : none                                               */
/*------------------------------------------------------------------*/
/* Returnvalue : unsigned char -> data from I2C device              */
/*------------------------------------------------------------------*/
/* History     : 11/99    V1.0 Basic routine                        */
/*                                                                  */
/********************************************************************/
unsigned char i2c_read( void ){

	unsigned char idata r = 0;  		// Return value with read I2C byte
	unsigned char idata c = 0;  		// Bit counter

	EA = 0;              		// Disable all interrupts for a accurate timing

	for( c = 8; c > 0; c-- ){
		SDA = 1;           		// Release SDA
		SCL = 1;           		// Release SCL
		while( SCL == 0 )
			;   					// Synchronize clock
		i2c_delay();       		// Force 5µs-delay

		r <<= 1;      			// Shift left the result

		if( SDA )
			r |= 0x01;  		// Set actual SDA state to LSB

		SCL = 0;           		// Force a clock cycle
		i2c_delay();       		// Force 5µs-delay
	}

	EA = 1;              		// Enable all interrupts

	// ACK or NACK from MASTER must be generated outside this routine

	return( r );

} /* end i2c_read() */

#endif	// end i2c bit-banged
