/*!
 * @file USI_TWI_Master.cpp
 */
/*****************************************************************************
*
*
* File              USI_TWI_Master.c compiled with gcc
* Date              Friday, 10/31/08		Boo!
* Updated by        jkl
*

* AppNote           : AVR310 - Using the USI module as a TWI Master
*
*		Extensively modified to provide complete I2C driver.
*
*Notes:
*		- T4_TWI and T2_TWI delays are modified to work with 1MHz
default clock *			and now use hard code values. They would need to
change *			for other clock rates. Refer to the Apps Note.
*
*	12/17/08	Added USI_TWI_Start_Memory_Read Routine		-jkl
*		Note msg buffer will have slave adrs ( with write bit set) and
memory adrs;
*			length should be these two bytes plus the number of
bytes to read.
****************************************************************************/
#include "USI_TWI_Master.h"
#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>

/*!
 * @brief USI Transmit and receive function
 * @param msg Pointer to the location of the msg buffer
 * @param msgSize How much data to send from the buffer
 * @return Returns true if transmission was successful
 */
unsigned char USI_TWI_Start_Transceiver_With_Data(unsigned char *,
                                                  unsigned char);
unsigned char USI_TWI_Master_Transfer(unsigned char);
unsigned char USI_TWI_Master_Start(void);

/*!
 * @brief Stores the state of the USI_TWI
 */
union USI_TWI_state {
  unsigned char errorState; //!< Can reuse the TWI_state for error states since
                            //!< it will not be needed if there is an error.
  /*!
   * @brief Struct that stores the modes for the device
   */
  struct {
    unsigned char addressMode : 1;         //!< Address mode
    unsigned char masterWriteDataMode : 1; //!< Write data mode
    unsigned char memReadMode : 1;         //!< Read memory mode
    unsigned char unused : 5;              //!< Unused
  };
} USI_TWI_state; //!< USI_TWI_state The state of the USI_TWI

/*!
 * @brief USI TWI single master initialization function
 */
void USI_TWI_Master_Initialise(void) {
  PORT_USI |=
      (1
       << PIN_USI_SDA); // Enable pullup on SDA, to set high as released state.
  PORT_USI |=
      (1
       << PIN_USI_SCL); // Enable pullup on SCL, to set high as released state.

  DDR_USI |= (1 << PIN_USI_SCL); // Enable SCL as output.
  DDR_USI |= (1 << PIN_USI_SDA); // Enable SDA as output.

  USIDR = 0xFF; // Preload dataregister with "released level" data.
  USICR = (0 << USISIE) | (0 << USIOIE) | // Disable Interrupts.
          (1 << USIWM1) | (0 << USIWM0) | // Set USI in Two-wire mode.
          (1 << USICS1) | (0 << USICS0) |
          (1 << USICLK) | // Software stobe as counter clock source
          (0 << USITC);
  USISR = (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) |
          (1 << USIDC) |    // Clear flags,
          (0x0 << USICNT0); // and reset counter.
}

/*!
 * @brief Use this function to get hold of the error message from the last
 * transmission
 * @return Returns error state
 */
unsigned char USI_TWI_Get_State_Info(void) {
  return (USI_TWI_state.errorState); // Return error state.
}
/*!
 * @brief USI Random (memory) Read function. This function sets up for call
 * to USI_TWI_Start_Transceiver_With_Data which does the work.
 * Doesn't matter if read/write bit is set or cleared, it'll be set
 * correctly in this function.
 *
 * The msgSize is passed to USI_TWI_Start_Transceiver_With_Data.
 *
 * Success or error code is returned. Error codes are defined in
 * USI_TWI_Master.h
 * @param msg Pointer to the buffer that contains the messages to be read
 * @param msgSize How much to read from the buffer
 * @return Returns the message read
 */
unsigned char USI_TWI_Start_Random_Read(unsigned char *msg,
                                        unsigned char msgSize) {
  *(msg) &= ~(TRUE << TWI_READ_BIT); // clear the read bit if it's set
  USI_TWI_state.errorState = 0;
  USI_TWI_state.memReadMode = TRUE;

  return (USI_TWI_Start_Transceiver_With_Data(msg, msgSize));
}
/*!
 * @brief USI Normal Read / Write Function
 * Transmit and receive function. LSB of first byte in buffer
 * indicates if a read or write cycles is performed. If set a read
 * operation is performed.
 *
 * Function generates (Repeated) Start Condition, sends address and
 * R/W, Reads/Writes Data, and verifies/sends ACK.
 *
 * Success or error code is returned. Error codes are defined in
 * USI_TWI_Master.h
 * @param msg Pointer to the buffer that has the messages
 * @param msgSize The size of the message
 * @return Returns the data read
 */
unsigned char USI_TWI_Start_Read_Write(unsigned char *msg,
                                       unsigned char msgSize) {

  USI_TWI_state.errorState = 0; // Clears all mode bits also

  return (USI_TWI_Start_Transceiver_With_Data(msg, msgSize));
}
/*!
 * @brief USI Transmit and receive function. LSB of first byte in buffer
 * indicates if a read or write cycles is performed. If set a read
 * operation is performed.
 *
 * Function generates (Repeated) Start Condition, sends address and
 * R/W, Reads/Writes Data, and verifies/sends ACK.
 *
 * This function also handles Random Read function if the memReadMode
 * bit is set. In that case, the function will:
 * The address in memory will be the second
 * byte and is written *without* sending a STOP.
 * Then the Read bit is set (lsb of first byte), the byte count is
 * adjusted (if needed), and the function function starts over by sending
 * the slave address again and reading the data.
 *
 * Success or error code is returned. Error codes are defined in
 * USI_TWI_Master.h
 * @param msg Pointer to the location of the msg buffer
 * @param msgSize How much data to send from the buffer
 */
unsigned char USI_TWI_Start_Transceiver_With_Data(unsigned char *msg,
                                                  unsigned char msgSize) {
  unsigned char const tempUSISR_8bit =
      (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) |
      (1 << USIDC) |    // Prepare register value to: Clear flags, and
      (0x0 << USICNT0); // set USI to shift 8 bits i.e. count 16 clock edges.
  unsigned char const tempUSISR_1bit =
      (1 << USISIF) | (1 << USIOIF) | (1 << USIPF) |
      (1 << USIDC) |    // Prepare register value to: Clear flags, and
      (0xE << USICNT0); // set USI to shift 1 bit i.e. count 2 clock edges.
  unsigned char *savedMsg;
  unsigned char savedMsgSize;

  // This clear must be done before calling this function so that memReadMode
  // can be specified.
  //  USI_TWI_state.errorState = 0;				// Clears all
  //  mode bits also

  USI_TWI_state.addressMode = TRUE; // Always true for first byte

#ifdef PARAM_VERIFICATION
  if (msg > (unsigned char *)RAMEND) // Test if address is outside SRAM space
  {
    USI_TWI_state.errorState = USI_TWI_DATA_OUT_OF_BOUND;
    return (FALSE);
  }
  if (msgSize <= 1) // Test if the transmission buffer is empty
  {
    USI_TWI_state.errorState = USI_TWI_NO_DATA;
    return (FALSE);
  }
#endif

#ifdef NOISE_TESTING // Test if any unexpected conditions have arrived prior to
                     // this execution.
  if (USISR & (1 << USISIF)) {
    USI_TWI_state.errorState = USI_TWI_UE_START_CON;
    return (FALSE);
  }
  if (USISR & (1 << USIPF)) {
    USI_TWI_state.errorState = USI_TWI_UE_STOP_CON;
    return (FALSE);
  }
  if (USISR & (1 << USIDC)) {
    USI_TWI_state.errorState = USI_TWI_UE_DATA_COL;
    return (FALSE);
  }
#endif

  if (!(*msg &
        (1 << TWI_READ_BIT))) // The LSB in the address byte determines if is a
                              // masterRead or masterWrite operation.
  {
    USI_TWI_state.masterWriteDataMode = TRUE;
  }

  //	if (USI_TWI_state.memReadMode)
  //	{
  savedMsg = msg;
  savedMsgSize = msgSize;
  //	}

  if (!USI_TWI_Master_Start()) {
    return (FALSE); // Send a START condition on the TWI bus.
  }

  /*Write address and Read/Write data */
  do {
    /* If masterWrite cycle (or inital address tranmission)*/
    if (USI_TWI_state.addressMode || USI_TWI_state.masterWriteDataMode) {
      /* Write a byte */
      PORT_USI &= ~(1 << PIN_USI_SCL);         // Pull SCL LOW.
      USIDR = *(msg++);                        // Setup data.
      USI_TWI_Master_Transfer(tempUSISR_8bit); // Send 8 bits on bus.

      /* Clock and verify (N)ACK from slave */
      DDR_USI &= ~(1 << PIN_USI_SDA); // Enable SDA as input.
      if (USI_TWI_Master_Transfer(tempUSISR_1bit) & (1 << TWI_NACK_BIT)) {
        if (USI_TWI_state.addressMode)
          USI_TWI_state.errorState = USI_TWI_NO_ACK_ON_ADDRESS;
        else
          USI_TWI_state.errorState = USI_TWI_NO_ACK_ON_DATA;
        return (FALSE);
      }

      if ((!USI_TWI_state.addressMode) &&
          USI_TWI_state
              .memReadMode) // means memory start address has been written
      {
        msg = savedMsg;                   // start at slave address again
        *(msg) |= (TRUE << TWI_READ_BIT); // set the Read Bit on Slave address
        USI_TWI_state.errorState = 0;
        USI_TWI_state.addressMode = TRUE; // Now set up for the Read cycle
        msgSize = savedMsgSize;           // Set byte count correctly
        // NOte that the length should be Slave adrs byte + # bytes to read + 1
        // (gets decremented below)
        if (!USI_TWI_Master_Start()) {
          USI_TWI_state.errorState = USI_TWI_BAD_MEM_READ;
          return (FALSE); // Send a START condition on the TWI bus.
        }
      } else {
        USI_TWI_state.addressMode =
            FALSE; // Only perform address transmission once.
      }
    }
    /* Else masterRead cycle*/
    else {
      /* Read a data byte */
      DDR_USI &= ~(1 << PIN_USI_SDA); // Enable SDA as input.
      *(msg++) = USI_TWI_Master_Transfer(tempUSISR_8bit);

      /* Prepare to generate ACK (or NACK in case of End Of Transmission) */
      if (msgSize == 1) // If transmission of last byte was performed.
      {
        USIDR = 0xFF; // Load NACK to confirm End Of Transmission.
      } else {
        USIDR = 0x00; // Load ACK. Set data register bit 7 (output for SDA) low.
      }
      USI_TWI_Master_Transfer(tempUSISR_1bit); // Generate ACK/NACK.
    }
  } while (--msgSize); // Until all data sent/received.

  // usually a stop condition is sent here, but TinyWireM needs to choose
  // whether or not to send it

  /* Transmission successfully completed*/
  return (TRUE);
}

/*!
 * @brief Core function for shifting data in and out from the USI.
 * Data to be sent has to be placed into the USIDR prior to calling
 * this function. Data read, will be return'ed from the function.
 * @param temp Temperature to set the USISR
 * @return Returns the temp read from the device
 */
unsigned char USI_TWI_Master_Transfer(unsigned char temp) {
  USISR = temp;                          // Set USISR according to temp.
                                         // Prepare clocking.
  temp = (0 << USISIE) | (0 << USIOIE) | // Interrupts disabled
         (1 << USIWM1) | (0 << USIWM0) | // Set USI in Two-wire mode.
         (1 << USICS1) | (0 << USICS0) |
         (1 << USICLK) | // Software clock strobe as source.
         (1 << USITC);   // Toggle Clock Port.
  do {
    _delay_us(T2_TWI);
    USICR = temp; // Generate positve SCL edge.
    while (!(PIN_USI & (1 << PIN_USI_SCL)))
      ; // Wait for SCL to go high.
    _delay_us(T4_TWI);
    USICR = temp;                     // Generate negative SCL edge.
  } while (!(USISR & (1 << USIOIF))); // Check for transfer complete.

  _delay_us(T2_TWI);
  temp = USIDR;                  // Read out data.
  USIDR = 0xFF;                  // Release SDA.
  DDR_USI |= (1 << PIN_USI_SDA); // Enable SDA as output.

  return temp; // Return the data from the USIDR
}
/*!
 * @brief Function for generating a TWI Start Condition.
 * @return Returns true if the signal can be verified, otherwise returns false
 */
unsigned char USI_TWI_Master_Start(void) {
  /* Release SCL to ensure that (repeated) Start can be performed */
  PORT_USI |= (1 << PIN_USI_SCL); // Release SCL.
  while (!(PORT_USI & (1 << PIN_USI_SCL)))
    ; // Verify that SCL becomes high.
  _delay_us(T2_TWI);

  /* Generate Start Condition */
  PORT_USI &= ~(1 << PIN_USI_SDA); // Force SDA LOW.
  _delay_us(T4_TWI);
  PORT_USI &= ~(1 << PIN_USI_SCL); // Pull SCL LOW.
  PORT_USI |= (1 << PIN_USI_SDA);  // Release SDA.

#ifdef SIGNAL_VERIFY
  if (!(USISR & (1 << USISIF))) {
    USI_TWI_state.errorState = USI_TWI_MISSING_START_CON;
    return (FALSE);
  }
#endif
  return (TRUE);
}
/*!
 * @brief Function for generating a TWI Stop Condition. Used to release
 * the TWI bus.
 * @return Returns true if it was successful
 */
unsigned char USI_TWI_Master_Stop(void) {
  PORT_USI &= ~(1 << PIN_USI_SDA); // Pull SDA low.
  PORT_USI |= (1 << PIN_USI_SCL);  // Release SCL.
  while (!(PIN_USI & (1 << PIN_USI_SCL)))
    ; // Wait for SCL to go high.
  _delay_us(T4_TWI);
  PORT_USI |= (1 << PIN_USI_SDA); // Release SDA.
  _delay_us(T2_TWI);

#ifdef SIGNAL_VERIFY
  if (!(USISR & (1 << USIPF))) {
    USI_TWI_state.errorState = USI_TWI_MISSING_STOP_CON;
    return (FALSE);
  }
#endif

  return (TRUE);
}
