/******************************************************************
 * i2c_driver.cpp | Arduino Duemilanove w/ ATmega328
 ******************************************************************
 * Author: Jonathan A. Feucht
 * Date created: 29 May 2010
 */

#include <WProgram.h>
#include <inttypes.h>
#include "common.h"
#include "i2c_driver.h"

void report_setting(setting_t, unsigned char);

setting_t cur_setting = SET_VOLUME;
unsigned char cur_value = 0;

unsigned char sync_code[2] = {
  I2C_MSG_SYNC1, I2C_MSG_SYNC2};

void setup_i2c() {
  // Initialize i2c communication
  i2c_init();

  // Report all settings to the pic
  i2c_report_all();

  return;
}

void i2c_report_all() {
  // Relay default settings to slave device
  report_setting(SET_VOLUME, volume_setting);
  report_setting(SET_REVERB, reverb_setting);
  report_setting(SET_CHORUS, chorus_setting);
  report_setting(SET_INSTR1, instr1_setting);

  return; 
}

void report_setting(setting_t setting, unsigned char value) {
  unsigned char i2c_packet[2] = {
    (unsigned char)setting, value
  };
  while (i2c_send_packet(i2c_packet, 2) != I2C_MSG_SUCCESS) {
    i2c_init();
  }
  return;
}

// Synchronize with slave device
void i2c_init() {
  unsigned char data;
  force_wire(SCL, HIGH);
  force_wire(SDA, HIGH);
  do {
    data = i2c_send_packet(sync_code, 2);
  } 
  while (data != I2C_MSG_SUCCESS);

  return;
}

// Sends an array of chars, return response character
unsigned char i2c_send_packet(unsigned char* first_char_ptr, unsigned int num_chars) {
  unsigned int i;
  unsigned char data;

  i2c_start();
  i2c_write(I2C_ADDR);      // Start write session
  for (i = 0; i < num_chars; i++) {
    i2c_write(first_char_ptr[i]);
  }
  i2c_stop();
  i2c_start();
  i2c_write(I2C_ADDR + 1);    // Start read session
  data = i2c_read(0);
  i2c_stop();

  return data;
} 

// Used to quickly drive an i2c communication line high or low
void force_wire(char pin, boolean value) {
  digitalWrite(pin, value); 
  pinMode(pin, OUTPUT);
  if (value) {
    pinMode(pin, INPUT);
    digitalWrite(pin, 0); 
  }

  return;
}

// Send communication request to the slave device
void i2c_start(void) {
  force_wire(SDA, 1);    // SDA = 1
  delay(I2C_DELAY_TIME);
  force_wire(SCL, 1);    // SCL = 1
  delay(I2C_DELAY_TIME);
  force_wire(SDA, 0);    // SDA = 0
  delay(I2C_DELAY_TIME);
  force_wire(SCL, 0);    // SCL = 0
  delay(I2C_DELAY_TIME);

  return;
}

// Stop communication with the slave device
void i2c_stop(void) {
  // Force SDA low
  force_wire(SDA, 0);    // SDA = 0
  delay(I2C_DELAY_TIME);
  force_wire(SCL, 1);    // SCL = 1
  delay(I2C_DELAY_TIME); 
  force_wire(SDA, 1);    // SDA = 1
  delay(I2C_DELAY_TIME);

  return;
}

// This method reads a byte of data from the i2c bus, and sends out
// an optional acknowledge bit. 
//
// Use acknowledge = 1 when there is more data that needs to be read,
// and acknowledge = 0 if this is th elast value being read
unsigned char i2c_read(boolean acknowledge) {
  char i;
  unsigned char data = 0;

  force_wire(SDA, HIGH);    // SDA = 1

  for(i=0; i < 8; i++) {
    // Allow SCL line to float
    pinMode(SCL, INPUT);
    // Wait for other device to drive SCL high
    while (!digitalRead(SCL));
    // Wait a little bit, so there is not a bus conflict
    delay(I2C_DELAY_TIME);
    // Read in the next bit
    data = (data << 1) + (unsigned char)digitalRead(SDA);
    // Force SCL wire low
    force_wire(SCL, LOW);  // SCL = 0
  }
  // Send acknowledge bit
  force_wire(SDA, !acknowledge);
  force_wire(SCL, HIGH);   // SCL = 1
  delay(I2C_DELAY_TIME);
  force_wire(SCL, LOW);    // SCL = 0
  force_wire(SDA, HIGH);   // SDA = 1

  return data;
}

// This method writes one byte of data to the i2c bus
// Acknowledge bit is returned
boolean i2c_write(unsigned char data) {
  unsigned char i;
  boolean acknowledge;

  // Loop through each bit in the byte, starting with
  // most significant bit
  for(i = 0x80; i > 0; i >>= 1) {
    // Toggle SDA, based on the current bit
    force_wire(SDA, data & i);
    // Toggle SCL on and off
    force_wire(SCL, HIGH);
    delay(I2C_DELAY_TIME);
    force_wire(SCL, LOW);
  }
  // Indicate the end of the byte
  force_wire(SDA, HIGH);
  force_wire(SCL, HIGH);
  delay(I2C_DELAY_TIME);
  // Read in acknowledge bit
  acknowledge = digitalRead(SDA);
  force_wire(SCL, LOW);

  return acknowledge;
}





