// Copyright 2010 Olivier Gillet.
//
// Author: Olivier Gillet (ol.gillet@gmail.com)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see .
//
// -----------------------------------------------------------------------------
//
// Implementation of the I2C protocol (Master mode only for now).
//
// Note that this file is not in the hal directory directly because I don't want
// the interrupt handler code for TWI to be linked with every project.
#ifndef AVRLIB_I2C_I2C_H_
#define AVRLIB_I2C_I2C_H_
#include 
#include "avrlib/gpio.h"
#include "avrlib/avrlib.h"
#include "avrlib/ring_buffer.h"
namespace avrlib {
IORegister(TWCR);
IORegister(TWSR);
typedef BitInRegister Prescaler0;
typedef BitInRegister Prescaler1;
typedef BitInRegister I2cEnable;
typedef BitInRegister I2cInterrupt;
typedef BitInRegister I2cAck;
typedef BitInRegister I2cStart;
typedef BitInRegister I2cStop;
enum I2cState {
  I2C_STATE_READY,
  I2C_STATE_TRANSMITTING,
  I2C_STATE_RECEIVING,
};
enum I2cError {
  I2C_ERROR_NONE = 0xff,
  I2C_ERROR_NO_ACK_FOR_ADDRESS = 0x01,
  I2C_ERROR_NO_ACK_FOR_DATA = TW_MT_SLA_NACK,
  I2C_ERROR_ARBITRATION_LOST = TW_MT_DATA_NACK,
  I2C_ERROR_BUS_ERROR = 0xfe,
  I2C_ERROR_TIMEOUT = 0x02,
};
template
class I2cOutput {
 public:
  I2cOutput() { }
  enum {
    buffer_size = output_buffer_size,
    data_size = 8
  };
  typedef typename DataTypeForSize::Type Value;
};
template
class I2cInput {
 public:
  I2cInput() { }
  enum {
    buffer_size = input_buffer_size,
    data_size = 8
  };
  typedef typename DataTypeForSize::Type Value;
};
// I2C Handler.
extern void (*i2c_handler_)();
template
class I2cMaster {
 public:
  I2cMaster() { }
  typedef typename DataTypeForSize::data_size>::Type Value;
  static void Init() {
    // Prescaler is set to a factor of 1.
    Prescaler0::clear();
    Prescaler1::clear();
    TWBR = (F_CPU / frequency - 16) / 2;
    I2cEnable::set();
    I2cInterrupt::set();
    I2cAck::set();
    state_ = I2C_STATE_READY;
    i2c_handler_ = &Handler;
  }
  
  static void Done() {
    I2cInterrupt::clear();
    I2cEnable::clear();
    I2cAck::clear();
    i2c_handler_ = NULL;
  }
  static uint8_t Wait() {
    while (state_ != I2C_STATE_READY) { }
    return error_;
  }
  
  static uint8_t Wait(uint16_t num_cycles) {
    while (state_ != I2C_STATE_READY && num_cycles) {
      --num_cycles;
    }
    if (!num_cycles) {
      error_ = I2C_ERROR_TIMEOUT;
    }
    return error_;
  }
  static uint8_t Send(uint8_t address) {
    // The output buffer is empty, no need to do anything.
    if (!Output::readable()) {
      return 0;
    }
    // Sorry, data can be sent only when the line is not busy.
    if (state_ != I2C_STATE_READY) {
      return 0;
    }
    error_ = I2C_ERROR_NONE;
    state_ = I2C_STATE_RECEIVING;
    slarw_ = (address << 1) | TW_WRITE;
    uint8_t size = Output::readable();
    I2cStart::set();
    return size;
  }
  static uint8_t Request(uint8_t address, uint8_t requested) {
    // Make sure that we don't request more than the buffer can hold.
    if (requested >= Input::writable()) {
      requested = Input::writable() - 1;
    }
    // Sorry, data can be requested only when the line is not busy.
    if (state_ != I2C_STATE_READY) {
      return 0;
    }
    error_ = I2C_ERROR_NONE;
    state_ = I2C_STATE_RECEIVING;
    slarw_ = (address << 1) | TW_READ;
    received_ = 0;
    requested_ = requested;
    I2cStart::set();
    
    return requested;
  }
  // All the read/write operations are done on the buffer, so they do not
  // block.
  static inline void Write(Value v) { Output::Write(v); }
  static inline uint8_t writable() { return Output::writable(); }
  static inline uint8_t NonBlockingWrite(Value v) {
    return Output::NonBlockingWrite(v);
  }
  static inline void Overwrite(Value v) { Output::Overwrite(v); }
  static inline Value Read() { return Input::Read(); }
  static inline uint8_t readable() { return Input::readable(); }
  static inline int16_t NonBlockingRead() { return Input::NonBlockingRead(); }
  static inline Value ImmediateRead() { return Input::ImmediateRead(); }
  static inline void FlushInputBuffer() { Input::Flush(); }
  static inline void FlushOutputBuffer() { Output::Flush(); }
 private:
  static inline void Continue(uint8_t ack) {
    if (ack) {
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT) | _BV(TWEA);
    } else {
      TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWINT);
    }
  }
  static inline void Stop() {
    I2cStop::set();
    while (I2cStop::value()) { }
    state_ = I2C_STATE_READY;
  }
  static inline void Abort() {
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT);
    state_ = I2C_STATE_READY;
  }
  static void Handler() {
    switch (TW_STATUS) {
      case TW_START:
      case TW_REP_START:
        TWDR = slarw_;
        Continue(1);
        break;
      case TW_MT_DATA_ACK:
      case TW_MT_SLA_ACK:
        if (Output::readable()) {
          TWDR = Output::ImmediateRead();
          Continue(1);
        } else {
          Stop();
        }
        break;
      case TW_MT_SLA_NACK:
      case TW_MT_DATA_NACK:
        error_ = TW_STATUS;
        Stop();
        break;
      case TW_MT_ARB_LOST:
        error_ = I2C_ERROR_ARBITRATION_LOST;
        Abort();
        break;
      case TW_MR_DATA_ACK:
        Input::Overwrite(TWDR);
        ++received_;
      case TW_MR_SLA_ACK:
        if (received_ < requested_) {
          Continue(1);
        } else {
          Continue(0);
        }
        break;
      case TW_MR_DATA_NACK:
        Input::Overwrite(TWDR);
        ++received_;
      case TW_MR_SLA_NACK:
        Stop();
        break;
      case TW_NO_INFO:
        break;
      case TW_BUS_ERROR:
        error_ = I2C_ERROR_BUS_ERROR;
        Stop();
        break;
    }
  }
public:
  typedef RingBuffer > Input;
  typedef RingBuffer > Output;
private:
  static volatile uint8_t state_;
  static volatile uint8_t error_;
  static volatile uint8_t slarw_;
  static volatile uint8_t received_;
  static uint8_t requested_;
  DISALLOW_COPY_AND_ASSIGN(I2cMaster);
};
/* static */
template
volatile uint8_t I2cMaster::state_;
/* static */
template
volatile uint8_t I2cMaster::error_;
/* static */
template
volatile uint8_t I2cMaster::slarw_;
/* static */
template
volatile uint8_t I2cMaster::received_;
/* static */
template
uint8_t I2cMaster::requested_;
}  // namespace avrlib
#endif   // AVRLIB_I2C_I2C_H_