/* bq.c: user interface for BQ gas gauges
 * Please make sure that kernel is configured with i2c-dev module loaded.  This presents i2c-X interfaces
 * that this code uses.
 */

#include "bq.h"

/*i2c-dev interface
 * We are reading and writing to /dev/i2c-X (2 in our case, since bus 2 is brought out: see #define in bq.h)
 * see i2c-dev.h for more interface details
 * open() to open file
 * read() to perform read: we cannot do this since we need an 8 bit subaddress
 * write() to perform a basic write
 * ioctl: to set slave addres and perform read/write combinations
 */


/* primary IOCTLs for I2C
 * I2C_SLAVE: Use this slave address
 * I2C_FUNCS: Get adapter capability mask: defined in 82c.h
 * I2C_RDWR: Combined R/W transfer: we need to use this to write the subaddress first and then read.
 */

/*
 * Initbq(): Initialize the i2c bus to communicate with the BQ gauge
 * Arguments: None
 * Returns: failure code if failed, otherwise ioctl success value (0)
 */
int initbq()
{
	char filename[20];
	int retval;
	
	curraddr = 0;
	//create the filename for our bus: we are using adapter 2: see bq.h for definition of ADAPTER_NR
	snprintf(filename, 19, "/dev/i2c-%d", ADAPTER_NR);
	
	//open the file and store it to our variable
	i2cfile = open(filename, O_RDWR);
	
	//if we failed, inform the reader and return the handle value
	if( i2cfile < 0) {
		printf("Couldread not open i2c device file\n");
		retval = i2cfile;
		return retval;
	}
	
	//set the slave address to the default slave address defined in bq.h
	 retval = ioctl(i2cfile,I2C_SLAVE,BQADDR);

	//if we fail, tell the user, close the file, and return the failure value
	if(retval  < 0) {
		printf("Could not set slave address\n");
		close(i2cfile);
		return retval;
	}
	
	//set our current address variable to the default address
	//this is necessary because the IOCTL_RDWR ioctl requires you to specify the address for each message
	//since this address might change (if we enter ROM mode on the BQ device), we need to keep track of
	//which one we are using.
	curraddr = BQADDR;
	return retval;
}

/*
 * seti2caddr: Sets the I2C address to a custom value
 * Should NOT be neccessary in normal operation.  Only needed if you enter ROM mode to reflash device.
 * Arguments: unsigned char addr: 8 bit address value (lower 7 bits are the address)
 * Returns: ioctl failure code on failure: 0 otherwise
 */
int seti2caddr(unsigned char addr)
{
	int retval;
	
	//ioctl to set slave address
	retval = ioctl(i2cfile,I2C_SLAVE,addr);

	//if we fail, tell the user and return the error code
	if(retval  < 0) {
		printf("Could not set slave address\n");
		close(i2cfile);
		return retval;
	}
	
	//set our current address variable to the new address
	//this is necessary because the IOCTL_RDWR ioctl requires you to specify the address for each message
	//since this address might change (if we enter ROM mode on the BQ device), we need to keep track of
	//which one we are using.
	curraddr = addr;
	return retval;
}

/*
 * writebq: write a string of bytes to the BQ gauge
 * Arguments:	unsigned char cmd: subaddress value: this is the address on the gauge that you are writing to
 * 	                    unsigned char * buf: pointer to string of bytes that you will write
 *                     int cnt: number of bytes you will write
 * Returns: write failure code on failure, otherwise number of bytes written 
 */
int writebq(unsigned char cmd, unsigned char* buf, int cnt)
{
	//set up a buffer to write both the command and the input buffer
	char *bufwr = malloc(cnt+1);
	int retval;
	
	bufwr[0] = cmd;
	memcpy(bufwr + 1,buf,cnt);
	
	//write the buffer
	retval = write(i2cfile,bufwr,cnt + 1);
	
	//if we fail, tell the user, close the file, and return the error code
	if( retval < 0)
	{
		printf("Could not write to I2C\n");
		close(i2cfile);
	}
	
	free(bufwr);
	return retval;
}
/*
 * readbq: reads bytes from BQ gauge
 * Arguments:	unsigned char cmd: subaddress: this is the address on the gauge that you will read from
 *                     unsigned char* buf: buffer that you will read into
 *                     int cnt: number of bytes to read into buffer
 */
int readbq(unsigned char cmd,unsigned char* buf, int cnt)
{
	i2c_rdwr_ioctl_data data;
	int retval;
	
	/* 
	 * NOTE: Struct i2c_rdwr_ioctl_data defined in i2c-dev.h
	 * struct i2c_msg *msgs;
	 * __u32 nmsgs;
	 */
	 
	 /*
	  * NOTE: Struct i2c_msg defined in: i2c.h
	  * __u16 addr: slave address
	  * __u16 flags:
	  * I2C_M_TEN  indicates 10 bit address
	  * I2C_M_RD read data from slave to master
	  * I2C_M_RECV_LEN: length will be first received byte
	  * __u16 len  message length
	  * __u8 *buf  pointer to message data
	  */
	  
	// set up our data structure
	memset(&data, 0, sizeof(data));
	
	data.msgs = malloc(2*sizeof(struct i2c_msg));
	data.nmsgs = 2;
	
	//write message: this is the sub address that we are reading from
	data.msgs[0].addr = curraddr;
	data.msgs[0].len = 1;
	data.msgs[0].flags = 0;
	data.msgs[0].buf = &cmd;
	
	//read message: now we read into the buffer
	data.msgs[1].addr = curraddr;
	data.msgs[1].len = cnt;
	data.msgs[1].flags = I2C_M_RD;
	data.msgs[1].buf = buf;
	
	//run the ioctl to perform the RDWR operation
	retval = ioctl(i2cfile, I2C_RDWR, &data);
	
	//on failure tell the user and return the error code.
	if(retval < 0)
	{
		printf("IOctl Failed\n");
	}

	free(data.msgs);
	
	return retval;
	
}

/*
 * readbqdataram: Reads entirity of BQ gauge dataram into a bq_dataram structure (defined in bq.h)
 * Arguments:	bq_dataram *dr: pointer to structure
 * Returns: ioctl failure code on fail, otherwise 0
 */
int readbqdataram(bq_dataram *dr)
{
	// set up the data structure
	i2c_rdwr_ioctl_data data;
	int retval;
	char cmd = 0x00;
	  
	memset(&data, 0, sizeof(data));
	memset(dr,0,sizeof(bq_dataram));
	
	data.msgs = malloc(2*sizeof(struct i2c_msg));
	data.nmsgs = 2;
	
	//read from address 0
	data.msgs[0].addr = curraddr;
	data.msgs[0].len = 1;
	data.msgs[0].flags = 0;
	data.msgs[0].buf = &cmd;
	
	//read directly into the bq_dataram structure:
	//Note that the bq_dataram structure is ordered such that reading byte-for-byte in order from the device
	//is the correct order for information to be copied into the structure.  This makes it faster/easier to copy 
	data.msgs[1].addr = curraddr;
	data.msgs[1].len = sizeof(bq_dataram);  //copy the right number of bytes
	data.msgs[1].flags = I2C_M_RD;
	data.msgs[1].buf = (char*)dr; //copy directly to the structure
	
	//perform the ioctl to actually perform the RDWR operation
	retval = ioctl(i2cfile,I2C_RDWR,&data);
	
	//on failure, tell the user and return an error code
	if(retval < 0)
	{
		printf("IOctl Failed\n");
	}

	free(data.msgs);
	return retval;
}

/*
 * closebq: close the BQ gauge handle
 * Arguments: void
 * Returns: 0
 */
int closebq()
{
	close(i2cfile);
	return 0;
}
