/*!
 * \file    utl/com/i2c_bb.h
 * \brief   A bit banking implementation of i2c bus inherited from
 *          i2c_i base class.
 *
 * Copyright (C) 2018 Christos Choutouridis
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#ifndef __utl_com_i2c_bb_h__
#define __utl_com_i2c_bb_h__

#include <utl/core/impl.h>
#include <utl/core/crtp.h>
#include <utl/com/i2c.h>


namespace utl {

/*!
 * \ingroup Communication
 * \brief   A bit banking implementation of i2c bus
 *          inherited from \ref i2c_i base class.
 * \sa   i2c_i
 */
//!@{

   /*!
    * \brief
    *    I2C bit banking interface template class providing
    *    an I2C using bit banking using CRTP.
    * \param   impl_t   The CRTP type (the derived/implementation class typename).
    */
   template <typename impl_t>
   class i2c_bb_i : public i2c_i<i2c_bb_i<impl_t>> {
      _CRTP_IMPL(impl_t);
      friend i2c_i<i2c_bb_i<impl_t>>;

   public:
      using type     = i2c_bb_i<impl_t>;  //!< Export type as identity meta-function
      using Sequence = typename i2c_i<type>::Sequence;
      //! SDA pin direction enumerator
      enum class SDAMode {
         INPUT =0,
         OUTPUT
      };

   /*!
    * \name Object lifetime
    */
   //!@{
   protected:
      //! \brief A default destructor, allow destructor from derived only
      ~i2c_bb_i () noexcept = default;
      //! \brief A default constructor
      i2c_bb_i (uint32_t clk) noexcept
            : usec_ {1000000/(2*clk)} { }
   //!@}

   /*!
    * \name Implementation requirements
    * \note
    *    In order for the implementation to have the following as private members
    *    it also need to declare this class as friend
    */
   //!@{
   private:
      /*!
       * Implementers's sda pin function
       * \param   st    In SDA_OUTPUT mode, selects the pin output state
       * \return        In SDA_INPUT mode return the pin input state
       */
      bool SDA (SDAMode mode, bool st) { return impl().SDA (mode, st); }
      void SCL (uint8_t st)      { impl().SCL (st); }    //!< Implementers's scl pin function
      void delay (uint32_t usec) { impl().delay(usec); } //!< Implementers's usec delay function
   //!@}

   /*!
    * \name Implementation of base requirements
    */
   //!@{
   private:
      uint32_t _clock () const { return 1000000/(2*usec_); }
      void _clock (uint32_t c) { usec_ = 1000000/(2*c); }

      void _start ();      //!< Send start functionality
      void _stop ();       //!< Send stop functionality
      byte_t _rx_data (bool ack, Sequence seq);
      bool   _tx_data (byte_t byte, Sequence seq);
      uint32_t usec_;      //!< half period of I2C bus
   //!@}
   };

   /*
    * ============= User functions ================
    */
   /*!
    * \brief
    *    Send a START bit to the bus
    * \return none
    */
   template <typename impl_t>
   void i2c_bb_i<impl_t>::_start (void) {
      //Initially set pins
      SDA (SDAMode::OUTPUT, 1);
      SCL (1);
         delay (usec_);
      SDA (SDAMode::OUTPUT, 0);
         delay (usec_);
      SCL (0);  //Clear Clock
   }

   /*!
    * \brief
    *    Send a START bit to the bus
    * \return none
    */
   template <typename impl_t>
   void i2c_bb_i<impl_t>::_stop (void) {
      //Stop bit Operation
      SDA (SDAMode::OUTPUT, 0);
      SCL (0);
      SCL (1);
         delay (usec_);
      SDA (SDAMode::OUTPUT, 1);
         delay (usec_);
   }

   /*!
    * \brief
    *    Receive a byte from the i2c bus.
    * \param  ack    Optional ack bit.
    *    \arg 1  ACK the reception
    *    \arg 0  Don't ACK the reception.
    * \param   seq   The operation sequence to execute
    *    \arg  Sequence::BYTE       Receive only the byte, do not send ack clock
    *    \arg  Sequence::ACK        Send only the ack bit
    *    \arg  Sequence::BYTEnACK   Receive the byte and send the ack bit
    * \return   The byte received.
    */
   template <typename impl_t>
   byte_t i2c_bb_i<impl_t>::_rx_data (bool ack, Sequence seq) {
      byte_t byte {0};
      //Initial conditions
      SCL (0);
      SDA (SDAMode::INPUT, 0);

      if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) {
         // read 8 data bits
         for (int i=8 ; i!=0 ; --i) {
            byte <<= 1;
            SCL (1);
               delay (usec_);
            byte |= SDA (SDAMode::INPUT, 0);
            SCL (0);
               delay (usec_);
         }
      }
      if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) {
         SDA (SDAMode::OUTPUT, !ack);    //Send (or not) ACK bit
         SCL (1);
            delay (usec_);
         SCL (0);       // Keep the bus busy
         SDA (SDAMode::OUTPUT, 0);
      }
      return byte;
   }

   /*!
    * \brief
    *    Transmit a byte to the i2c bus.
    * \param  byte   The byte to send.
    * \param   seq   The operation sequence to execute
    *    \arg  Sequence::BYTE       Transmit only the byte, do not read ack bit
    *    \arg  Sequence::ACK        Read only the ack bit
    *    \arg  Sequence::BYTEnACK   Transmit the byte and read the ack bit
    * \return Slave's ACK bit
    *    \arg false  Slave didn't ACK
    *    \arg true   Slave did ACK
    */
   template <typename impl_t>
   bool i2c_bb_i<impl_t>::_tx_data (byte_t byte, Sequence seq) {
      bool ack {false};
      //Initial conditions
      SCL (0);
      SDA (SDAMode::OUTPUT, 0);

      if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) {
         //Send 8 bit data
         for (int i=8 ; i!=0 ; --i) {
            //Send MSB
            SDA (SDAMode::OUTPUT, byte & 0x80);
            byte <<= 1;
            SCL (1);
               delay (usec_);
            SCL (0);
               delay (usec_);
         }
      }
      if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) {
         // Get ACK
         SDA (SDAMode::INPUT, 0);
         SCL (1);
            delay (usec_);
         ack = !SDA (SDAMode::INPUT, 0);
         SCL (0);     // Keep the bus busy
            delay (usec_);
         SDA (SDAMode::OUTPUT, 0);
      }
      return ack;
   }


   /*!
    * \brief
    *    A virtual base class interface specialization.
    * Using the private virtual interface we provide the interface from
    * i2c_i<virtual_tag>
    * \param   impl_t   = virtual_tag
    */
   template<>
   class i2c_bb_i<virtual_tag> : public i2c_i<virtual_tag> {
   public:
      using type     = i2c_bb_i<virtual_tag>;   //!< Export type as identity meta-function
      using Sequence = typename i2c_i<virtual_tag>::Sequence;
      //! SDA pin direction enumerator
      enum class SDAMode {
         INPUT =0,
         OUTPUT
      };

   /*!
    * \name Object lifetime
    */
   //!@{
   protected:
      //! \brief Constructor
      i2c_bb_i (uint32_t clk) noexcept
            : usec_ {1000000/(2*clk)} { }
      //! \brief Virtual destructor
      virtual ~i2c_bb_i () noexcept  = default;
   //!@}

   /*!
    * \name Implementation requirements
    */
   //!@{
   private:
      /*!
       * Implementers's sda pin function
       * \param   st    In SDA_OUTPUT mode, selects the pin output state
       * \return        In SDA_INPUT mode return the pin input state
       */
      virtual bool SDA (SDAMode mode, bool st) =0;
      virtual void SCL (bool st) =0;            //!< Implementers's scl pin function
      virtual void delay (uint32_t usec) =0;    //!< Implementers's usec delay function
   //!@}

   /*!
    * \name Implementation of base requirements
    */
   //!@{
   private:
      uint32_t _clock () const final { return 1000000/(2*usec_); }
      void _clock (uint32_t c) final { usec_ = 1000000/(2*c); }

      void _start () final;
      void _stop () final;
      byte_t _rx_data (bool ack, Sequence seq) final;
      bool _tx_data (byte_t byte, Sequence seq) final;
      //! half period of I2C bus
      uint32_t usec_;
   //!@}
   };

   /*!
    * \brief
    *    Send a START bit to the bus
    * \return none
    */
   void i2c_bb_i<virtual_tag>::_start (void) {
      //Initially set pins
      SDA (SDAMode::OUTPUT, 1);
      SCL (1);
         delay (usec_);
      SDA (SDAMode::OUTPUT, 0);
         delay (usec_);
      SCL (0);  //Clear Clock
   }

   /*!
    * \brief
    *    Send a START bit to the bus
    * \return none
    */
   void i2c_bb_i<virtual_tag>::_stop (void) {
      //Stop bit Operation
      SDA (SDAMode::OUTPUT, 0);
      SCL (0);
      SCL (1);
         delay (usec_);
      SDA (SDAMode::OUTPUT, 1);
         delay (usec_);
   }

   /*!
    * \brief
    *    Receive a byte from the i2c bus.
    * \param  ack    Optional ack bit.
    *    \arg 1  ACK the reception
    *    \arg 0  Don't ACK the reception.
    * \param   seq   The operation sequence to execute
    *    \arg  Sequence::BYTE       Receive only the byte, do not send ack clock
    *    \arg  Sequence::ACK        Send only the ack bit
    *    \arg  Sequence::BYTEnACK   Receive the byte and send the ack bit
    * \return   The byte received.
    */
   byte_t i2c_bb_i<virtual_tag>::_rx_data (bool ack, Sequence seq) {
      byte_t byte {0};
      //Initial conditions
      SCL (0);
      SDA (SDAMode::INPUT, 0);

      if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) {
         // read 8 data bits
         for (int i=8 ; i!=0 ; --i) {
            byte <<= 1;
            SCL (1);
               delay (usec_);
            byte |= SDA (SDAMode::INPUT, 0);
            SCL (0);
               delay (usec_);
         }
      }
      if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) {
         SDA (SDAMode::OUTPUT, !ack);    //Send (or not) ACK bit
         SCL (1);
            delay (usec_);
         SCL (0);       // Keep the bus busy
         SDA (SDAMode::OUTPUT, 0);
      }
      return byte;
   }

   /*!
    * \brief
    *    Transmit a byte to the i2c bus.
    * \param  byte   The byte to send.
    * \param   seq   The operation sequence to execute
    *    \arg  Sequence::BYTE       Transmit only the byte, do not read ack bit
    *    \arg  Sequence::ACK        Read only the ack bit
    *    \arg  Sequence::BYTEnACK   Transmit the byte and read the ack bit
    * \return Slave's ACK bit
    *    \arg false  Slave didn't ACK
    *    \arg true   Slave did ACK
    */
   bool i2c_bb_i<virtual_tag>::_tx_data (byte_t byte, Sequence seq) {
      bool ack {false};
      //Initial conditions
      SCL (0);
      SDA (SDAMode::OUTPUT, 0);

      if (seq == Sequence::BYTE || seq == Sequence::BYTEnACK) {
         //Send 8 bit data
         for (int i=8 ; i!=0 ; --i) {
            //Send MSB
            SDA (SDAMode::OUTPUT, byte & 0x80);
            byte <<= 1;
            SCL (1);
               delay (usec_);
            SCL (0);
               delay (usec_);
         }
      }
      if (seq == Sequence::ACK || seq == Sequence::BYTEnACK) {
         // Get ACK
         SDA (SDAMode::INPUT, 0);
         SCL (1);
            delay (usec_);
         ack = !SDA (SDAMode::INPUT, 0);
         SCL (0);     // Keep the bus busy
            delay (usec_);
         SDA (SDAMode::OUTPUT, 0);
      }
      return ack;
   }

//!@}
}  // namspace utl

#endif   // #ifndef __utl_com_i2c_bb_h__
