//***************************************************
// pcf8591nd.c
// 
// Example to read A/D values from a 
// 4 channel / 8 bit AD converter PCF8591
// through I2C using bit banging method in user space
// http://www.acmesystems.it/?id=10
//***************************************************

#include "stdio.h"     
#include "unistd.h"    
#include "sys/ioctl.h"
#include "fcntl.h"     
#include "time.h"     
#include "linux/gpio_syscalls.h"

#define I2C_DATA_LINE   1<<24   // IOG24 J7.21
#define I2C_CLOCK_LINE  1<<25   //  OG25 J7.13
#define I2C_DATA_PORT   PORTG
#define I2C_CLOCK_PORT  PORTG

// Get the SDA line state

int i2c_getbit(void) {
  return (gpiogetbits(I2C_DATA_PORT,I2C_DATA_LINE)?(1):(0));
} 

// Set the SDA line as output

void i2c_dir_out(void) {
  gpiosetdir(I2C_DATA_PORT,DIROUT,I2C_DATA_LINE);
}

// Set the SDA line as input

void i2c_dir_in(void) {
  gpiosetdir(I2C_DATA_PORT,DIRIN,I2C_DATA_LINE);
}

// Set the SDA line state

void i2c_data(int state) {
  if (state==1) {
    i2c_dir_in(); 
  } else {  
    i2c_dir_out();  
    gpioclearbits(I2C_DATA_PORT, I2C_DATA_LINE);  
  } 
}

// Set the SCL line state

void i2c_clk(int state) {
  if (state==1) gpiosetbits(I2C_CLOCK_PORT, I2C_CLOCK_LINE);
  else gpioclearbits(I2C_CLOCK_PORT, I2C_CLOCK_LINE); 
}

// Read a byte from I2C bus and send the ack sequence
// Put islast = 1 is this is the last byte to receive from the slave

unsigned char i2c_inbyte(int islast) {
  unsigned char value = 0;
  int bitvalue;
  int i;

  // Read data byte

  i2c_clk(0);
  i2c_dir_in();

  for (i=0;i<8;i++) {
    i2c_clk(1);

    bitvalue = i2c_getbit();
    value |= bitvalue;
    if (i<7) value <<= 1;
    
    i2c_clk(0);
  }
  
  if (islast==0) {
    // Send Ack if is not the last byte to read
  
    i2c_dir_out();
    i2c_data(0);
    i2c_clk(1);
    i2c_clk(0);
    i2c_dir_in();
  } else {
    // Doesn't send Ack if is the last byte to read
    i2c_dir_in();
    i2c_clk(1);
    i2c_clk(0);
  }

  return value;
}


// Initializate the I2C bus

void i2c_init(void) {
  i2c_dir_in();
  i2c_clk(1);
}

// Send a start sequence to I2C bus

void i2c_start(void){
  i2c_clk(0);
  i2c_data(1);
  i2c_clk(1);
  i2c_data(0);
}

// Send a stop sequence to I2C bus

void i2c_stop(void) {
  i2c_clk(0);
  i2c_data(0);
  i2c_clk(1);
  i2c_data(1);
}

// Send a byte to the I2C bus and return the ack sequence from slave
// rtc
//  0 = Nack, 1=Ack

int i2c_outbyte(unsigned char x) {
  int i;
  int ack;

  i2c_clk(0);

  for (i=0;i<8;i++) {
    if (x & 0x80) i2c_data(1);
    else  i2c_data(0);
    i2c_clk(1);
    i2c_clk(0);
    x <<= 1;
  }

  i2c_dir_in();
  i2c_clk(1);
  ack=i2c_getbit();
  i2c_clk(0);
  
  if (ack==0) return 1;
  else return 0;
}

int main(void) {
  int ch;
  int i2c_slave;
  int ad_value;

  i2c_init();

  // PCF8591 address scheme
  // |  1 |  0 |  0 |  1 | A2 | A1 | A0 | R/W |
  i2c_slave =(0x09<<4)|(0x01<<1);

  for (ch=0;ch<=3;ch++) {  
    // Select the A/D channel
    i2c_start();
    if (i2c_outbyte(i2c_slave|0)==0) {
      printf("NACK received %d\n",__LINE__);
      i2c_stop();
      return -1;
    }
    if (i2c_outbyte(ch)==0) {
      printf("NACK received %d\n",__LINE__);
      i2c_stop();
      return -1;
    }
    i2c_stop();

    i2c_start();
    if (i2c_outbyte(i2c_slave|1)==0) {
      printf("NACK received %d\n",__LINE__);
      i2c_stop();
      return -1;
    }
    i2c_inbyte(0);
    i2c_inbyte(0);
    ad_value=i2c_inbyte(1);
    i2c_stop();

    // Show the voltage level
    printf("Chanel %d = %.2fv (%02X hex)\n",ch,ad_value*0.012941,ad_value);
  }
  
  return 0;
} 
