/***************************************************************************//**
 * @file
 * @brief Inter-integrated Circuit (I2C) Peripheral API
 *******************************************************************************
 * # License
 * <b>Copyright 2018 Silicon Laboratories Inc. www.silabs.com</b>
 *******************************************************************************
 *
 * SPDX-License-Identifier: Zlib
 *
 * The licensor of this software is Silicon Laboratories Inc.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 ******************************************************************************/

#include "em_i2c.h"
#if defined(I2C_COUNT) && (I2C_COUNT > 0)

#include "em_cmu.h"
#include "em_bus.h"
#include "sl_assert.h"

 #include <limits.h>

/***************************************************************************//**
 * @addtogroup i2c I2C - Inter-Integrated Circuit
 * @brief Inter-integrated Circuit (I2C) Peripheral API
 * @details
 *  This module contains functions to control the I2C peripheral of Silicon
 *  Labs 32-bit MCUs and SoCs. The I2C interface allows communication on I2C
 *  buses with the lowest energy consumption possible.
 * @{
 ******************************************************************************/

/*******************************************************************************
 *******************************   DEFINES   ***********************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/** Validation of the I2C register block pointer reference for assert statements. */
#if (I2C_COUNT == 1)
#define I2C_REF_VALID(ref)    ((ref) == I2C0)
#elif (I2C_COUNT == 2)
#define I2C_REF_VALID(ref)    (((ref) == I2C0) || ((ref) == I2C1))
#elif (I2C_COUNT == 3)
#define I2C_REF_VALID(ref)    (((ref) == I2C0) || ((ref) == I2C1) || ((ref) == I2C2))
#elif (I2C_COUNT == 4)
#define I2C_REF_VALID(ref)    (((ref) == I2C0) || ((ref) == I2C1) || ((ref) == I2C2) || ((ref) == I2C3))
#endif

/** Error flags indicating that the I2C transfer has failed. */
/* Notice that I2C_IF_TXOF (transmit overflow) is not really possible with */
/* the software-supporting master mode. Likewise, for I2C_IF_RXUF (receive underflow) */
/* RXUF is only likely to occur with the software if using a debugger peeking into */
/* the RXDATA register. Therefore, those types of faults are ignored. */
#define I2C_IF_ERRORS    (I2C_IF_BUSERR | I2C_IF_ARBLOST)
#define I2C_IEN_ERRORS   (I2C_IEN_BUSERR | I2C_IEN_ARBLOST)

/* Maximum I2C transmission rate constant.  */
#if defined(_SILICON_LABS_32B_SERIES_0)
#if defined(_EFM32_HAPPY_FAMILY) || defined(_EFM32_ZERO_FAMILY)
#define I2C_CR_MAX       8
#else
#define I2C_CR_MAX       4
#endif
#elif defined(_SILICON_LABS_32B_SERIES_1)
#define I2C_CR_MAX       8
#elif defined(_SILICON_LABS_32B_SERIES_2)
#define I2C_CR_MAX       8
#else
#warning "Max I2C transmission rate constant is not defined"
#endif

/** @endcond */

/*******************************************************************************
 ********************************   ENUMS   ************************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/** Master mode transfer states. */
typedef enum {
  i2cStateStartAddrSend,       /**< Send start + (first part of) address. */
  i2cStateAddrWFAckNack,       /**< Wait for ACK/NACK on (the first part of) address. */
  i2cStateAddrWF2ndAckNack,    /**< Wait for ACK/NACK on the second part of a 10 bit address. */
  i2cStateRStartAddrSend,      /**< Send a repeated start + (first part of) address. */
  i2cStateRAddrWFAckNack,      /**< Wait for ACK/NACK on an address sent after a repeated start. */
  i2cStateDataSend,            /**< Send data. */
  i2cStateDataWFAckNack,       /**< Wait for ACK/NACK on data sent. */
  i2cStateWFData,              /**< Wait for data. */
  i2cStateWFStopSent,          /**< Wait for STOP to have been transmitted. */
  i2cStateDone                 /**< Transfer completed successfully. */
} I2C_TransferState_TypeDef;

/** @endcond */

/*******************************************************************************
 *******************************   STRUCTS   ***********************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/** Structure used to store state information on an ongoing master mode transfer. */
typedef struct {
  /** Current state. */
  I2C_TransferState_TypeDef  state;

  /** Result return code. */
  I2C_TransferReturn_TypeDef result;

  /** Offset in the current sequence buffer. */
  uint16_t                   offset;

  /* Index to the current sequence buffer in use. */
  uint8_t                    bufIndx;

  /** Reference to the I2C transfer sequence definition provided by the user. */
  I2C_TransferSeq_TypeDef    *seq;
} I2C_Transfer_TypeDef;

/** @endcond */

/*******************************************************************************
 *****************************   LOCAL DATA   *******^**************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/**
 * Lookup table for Nlow + Nhigh setting defined by CLHR. Set the undefined
 * index (0x3) to reflect a default setting just in case.
 */
static const uint8_t i2cNSum[] = { 4 + 4, 6 + 3, 11 + 6, 4 + 4 };

/** A transfer state information for an ongoing master mode transfer. */
static I2C_Transfer_TypeDef i2cTransfer[I2C_COUNT];

/** @endcond */

/*******************************************************************************
 **************************   LOCAL FUNCTIONS   *******************************
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/***************************************************************************//**
 * @brief
 *   Empty received data buffer.
 ******************************************************************************/
static void flushRx(I2C_TypeDef *i2c)
{
  while (i2c->STATUS & I2C_STATUS_RXDATAV) {
    i2c->RXDATA;
  }

#if defined(_SILICON_LABS_32B_SERIES_2)
  /* SW needs to clear RXDATAV IF on Series 2 devices.
     Flag is kept high by HW if buffer is not empty. */
  I2C_IntClear(i2c, I2C_IF_RXDATAV);
#endif
}

/** @endcond */

/*******************************************************************************
 **************************   GLOBAL FUNCTIONS   *******************************
 ******************************************************************************/

/***************************************************************************//**
 * @brief
 *   Get the current configured I2C bus frequency.
 *
 * @details
 *   This frequency is only relevant when acting as master.
 *
 * @note
 *   The actual frequency is a real number, this function returns a rounded
 *   down (truncated) integer value.
 *
 * @param[in] i2c
 *   A pointer to the I2C peripheral register block.
 *
 * @return
 *   The current I2C frequency in Hz.
 ******************************************************************************/
uint32_t I2C_BusFreqGet(I2C_TypeDef *i2c)
{
  uint32_t freqHfper = 0;
  uint32_t n;

  /* Maximum frequency is given by freqScl = freqHfper/((Nlow + Nhigh)(DIV + 1) + I2C_CR_MAX)
   * For more details, see the reference manual
   * I2C Clock Generation chapter. */
  if (i2c == I2C0) {
    freqHfper = CMU_ClockFreqGet(cmuClock_I2C0);
#if defined(I2C1)
  } else if (i2c == I2C1) {
    freqHfper = CMU_ClockFreqGet(cmuClock_I2C1);
#endif
#if defined(I2C2)
  } else if (i2c == I2C2) {
    freqHfper = CMU_ClockFreqGet(cmuClock_I2C2);
#endif
  } else {
    EFM_ASSERT(false);
  }

  /* n = Nlow + Nhigh */
  n = (uint32_t)i2cNSum[(i2c->CTRL & _I2C_CTRL_CLHR_MASK)
                        >> _I2C_CTRL_CLHR_SHIFT];
  return freqHfper / ((n * (i2c->CLKDIV + 1)) + I2C_CR_MAX);
}

/***************************************************************************//**
 * @brief
 *   Set the I2C bus frequency.
 *
 * @details
 *   The bus frequency is only relevant when acting as master. The bus
 *   frequency should not be set higher than the maximum frequency accepted by the
 *   slowest device on the bus.
 *
 *   Notice that, due to asymmetric requirements on low and high I2C clock
 *   cycles in the I2C specification, the maximum frequency allowed
 *   to comply with the specification may be somewhat lower than expected.
 *
 *   See the reference manual, details on I2C clock generation,
 *   for maximum allowed theoretical frequencies for different modes.
 *
 * @param[in] i2c
 *   A pointer to the I2C peripheral register block.
 *
 * @param[in] freqRef
 *   An I2C reference clock frequency in Hz that will be used. If set to 0,
 *   HFPERCLK / HFPERCCLK clock is used. Setting it to a higher than actual
 *   configured value has the consequence of reducing the real I2C frequency.
 *
 * @param[in] freqScl
 *   A bus frequency to set (bus speed may be lower due to integer
 *   prescaling). Safe (according to the I2C specification) maximum frequencies for
 *   standard fast and fast+ modes are available using I2C_FREQ_ defines.
 *   (Using I2C_FREQ_ defines requires corresponding setting of @p type.)
 *   The slowest slave device on a bus must always be considered.
 *
 * @param[in] i2cMode
 *   A clock low-to-high ratio type to use. If not using i2cClockHLRStandard,
 *   make sure all devices on the bus support the specified mode. Using a
 *   non-standard ratio is useful to achieve a higher bus clock in fast and
 *   fast+ modes.
 ******************************************************************************/
void I2C_BusFreqSet(I2C_TypeDef *i2c,
                    uint32_t freqRef,
                    uint32_t freqScl,
                    I2C_ClockHLR_TypeDef i2cMode)
{
  uint32_t n, minFreq, denominator;
  int32_t div;

  /* Avoid dividing by 0. */
  EFM_ASSERT(freqScl);
  if (!freqScl) {
    return;
  }

  /* Ensure mode is valid */
  i2cMode &= _I2C_CTRL_CLHR_MASK >> _I2C_CTRL_CLHR_SHIFT;

  /* Set the CLHR (clock low-to-high ratio). */
  i2c->CTRL &= ~_I2C_CTRL_CLHR_MASK;
  BUS_RegMaskedWrite(&i2c->CTRL,
                     _I2C_CTRL_CLHR_MASK,
                     i2cMode << _I2C_CTRL_CLHR_SHIFT);

  if (freqRef == 0) {
    if (i2c == I2C0) {
      freqRef = CMU_ClockFreqGet(cmuClock_I2C0);
#if defined(I2C1)
    } else if (i2c == I2C1) {
      freqRef = CMU_ClockFreqGet(cmuClock_I2C1);
#endif
#if defined(I2C2)
    } else if (i2c == I2C2) {
      freqRef = CMU_ClockFreqGet(cmuClock_I2C2);
#endif
    } else {
      EFM_ASSERT(false);
    }
  }

  /* Check the minumum HF peripheral clock. */
  minFreq = UINT_MAX;
  if (i2c->CTRL & I2C_CTRL_SLAVE) {
    switch (i2cMode) {
      case i2cClockHLRStandard:
#if defined(_SILICON_LABS_32B_SERIES_0)
        minFreq = 4200000; break;
#elif defined(_SILICON_LABS_32B_SERIES_1)
        minFreq = 2000000; break;
#elif defined(_SILICON_LABS_32B_SERIES_2)
        minFreq = 2000000; break;
#endif
      case i2cClockHLRAsymetric:
#if defined(_SILICON_LABS_32B_SERIES_0)
        minFreq = 11000000; break;
#elif defined(_SILICON_LABS_32B_SERIES_1)
        minFreq = 5000000; break;
#elif defined(_SILICON_LABS_32B_SERIES_2)
        minFreq = 5000000; break;
#endif
      case i2cClockHLRFast:
#if defined(_SILICON_LABS_32B_SERIES_0)
        minFreq = 24400000; break;
#elif defined(_SILICON_LABS_32B_SERIES_1)
        minFreq = 14000000; break;
#elif defined(_SILICON_LABS_32B_SERIES_2)
        minFreq = 14000000; break;
#endif
      default:
        /* MISRA requires the default case. */
        break;
    }
  } else {
    /* For master mode, platform 1 and 2 share the same
       minimum frequencies. */
    switch (i2cMode) {
      case i2cClockHLRStandard:
        minFreq = 2000000; break;
      case i2cClockHLRAsymetric:
        minFreq = 9000000; break;
      case i2cClockHLRFast:
        minFreq = 20000000; break;
      default:
        /* MISRA requires default case */
        break;
    }
  }

  /* Frequency most be larger-than. */
  EFM_ASSERT(freqRef > minFreq);

  /* SCL frequency is given by:
   * freqScl = freqRef/((Nlow + Nhigh) * (DIV + 1) + I2C_CR_MAX)
   *
   * Therefore,
   * DIV = ((freqRef - (I2C_CR_MAX * freqScl))/((Nlow + Nhigh) * freqScl)) - 1
   *
   * For more details, see the reference manual
   * I2C Clock Generation chapter.  */

  /* n = Nlow + Nhigh */
  n = (uint32_t)i2cNSum[i2cMode];
  denominator = n * freqScl;

  /* Explicitly ensure denominator is never zero. */
  if (denominator == 0) {
    EFM_ASSERT(0);
    return;
  }
  /* Perform integer division so that div is rounded up. */
  div = (int32_t)(((freqRef - (I2C_CR_MAX * freqScl) + denominator - 1)
                   / denominator) - 1);
  EFM_ASSERT(div >= 0);
  EFM_ASSERT((uint32_t)div <= _I2C_CLKDIV_DIV_MASK);

  /* The clock divisor must be at least 1 in slave mode according to the reference */
  /* manual (in which case there is normally no need to set the bus frequency). */
  if ((i2c->CTRL & I2C_CTRL_SLAVE) && (div == 0)) {
    div = 1;
  }
  i2c->CLKDIV = (uint32_t)div;
}

/***************************************************************************//**
 * @brief
 *   Enable/disable I2C.
 *
 * @note
 *   After enabling the I2C (from being disabled), the I2C is in BUSY state.
 *
 * @param[in] i2c
 *   A pointer to the I2C peripheral register block.
 *
 * @param[in] enable
 *   True to enable counting, false to disable.
 ******************************************************************************/
void I2C_Enable(I2C_TypeDef *i2c, bool enable)
{
  EFM_ASSERT(I2C_REF_VALID(i2c));

#if defined (_I2C_EN_MASK)
  BUS_RegBitWrite(&(i2c->EN), _I2C_EN_EN_SHIFT, enable);
#else
  BUS_RegBitWrite(&(i2c->CTRL), _I2C_CTRL_EN_SHIFT, enable);
#endif
}

/***************************************************************************//**
 * @brief
 *   Initialize I2C.
 *
 * @param[in] i2c
 *   A pointer to the I2C peripheral register block.
 *
 * @param[in] init
 *   A pointer to the I2C initialization structure.
 ******************************************************************************/
void I2C_Init(I2C_TypeDef *i2c, const I2C_Init_TypeDef *init)
{
  EFM_ASSERT(I2C_REF_VALID(i2c));

  i2c->IEN = 0;
  I2C_IntClear(i2c, _I2C_IF_MASK);

  /* Set SLAVE select mode. */
  BUS_RegBitWrite(&(i2c->CTRL), _I2C_CTRL_SLAVE_SHIFT, init->master ? 0 : 1);

  I2C_BusFreqSet(i2c, init->refFreq, init->freq, init->clhr);

  I2C_Enable(i2c, init->enable);
}

/***************************************************************************//**
 * @brief
 *   Reset I2C to the same state that it was in after a hardware reset.
 *
 * @note
 *   The ROUTE register is NOT reset by this function to allow for
 *   centralized setup of this feature.
 *
 * @param[in] i2c
 *   A pointer to the I2C peripheral register block.
 ******************************************************************************/
void I2C_Reset(I2C_TypeDef *i2c)
{
  // Cancel ongoing operations and clear TX buffer
  i2c->CMD       = I2C_CMD_CLEARPC | I2C_CMD_CLEARTX | I2C_CMD_ABORT;
  i2c->CTRL      = _I2C_CTRL_RESETVALUE;
  i2c->CLKDIV    = _I2C_CLKDIV_RESETVALUE;
  i2c->SADDR     = _I2C_SADDR_RESETVALUE;
  i2c->SADDRMASK = _I2C_SADDRMASK_RESETVALUE;
  i2c->IEN       = _I2C_IEN_RESETVALUE;
#if defined (_I2C_EN_EN_MASK)
  i2c->EN      = _I2C_EN_RESETVALUE;
#endif

  // Empty received data buffer
  flushRx(i2c);
  I2C_IntClear(i2c, _I2C_IF_MASK);
  /* Do not reset the route register; setting should be done independently. */
}

// *****************************************************************************
/// @brief
///   Continue an initiated I2C transfer (single master mode only).
///
/// @details
///   This function is used repeatedly after a I2C_TransferInit() to
///   complete a transfer. It may be used in polled mode as the below example
///   shows:
/// @code{.c}
/// I2C_TransferReturn_TypeDef ret;
///
/// // Do a polled transfer
/// ret = I2C_TransferInit(I2C0, seq);
/// while (ret == i2cTransferInProgress)
/// {
///   ret = I2C_Transfer(I2C0);
/// }
/// @endcode
///  It may also be used in interrupt driven mode, where this function is invoked
///  from the interrupt handler. Notice that, if used in interrupt mode, NVIC
///  interrupts must be configured and enabled for the I2C bus used. I2C
///  peripheral specific interrupts are managed by this software.
///
/// @note
///   Only single master mode is supported.
///
/// @param[in] i2c
///   A pointer to the I2C peripheral register block.
///
/// @return
///   Returns status for an ongoing transfer.
///   @li #i2cTransferInProgress - indicates that transfer not finished.
///   @li #i2cTransferDone - transfer completed successfully.
///   @li otherwise some sort of error has occurred.
///
// *****************************************************************************
I2C_TransferReturn_TypeDef I2C_Transfer(I2C_TypeDef *i2c)
{
  uint32_t                tmp;
  uint32_t                pending;
  I2C_Transfer_TypeDef    *transfer;
  I2C_TransferSeq_TypeDef *seq;
  bool finished = false;

  EFM_ASSERT(I2C_REF_VALID(i2c));

  /* Support up to 2 I2C buses. */
  if (i2c == I2C0) {
    transfer = i2cTransfer;
  }
#if (I2C_COUNT > 1)
  else if (i2c == I2C1) {
    transfer = i2cTransfer + 1;
  }
#endif
#if (I2C_COUNT > 2)
  else if (i2c == I2C2) {
    transfer = i2cTransfer + 2;
  }
#endif
#if (I2C_COUNT > 3)
  else if (i2c == I2C3) {
    transfer = i2cTransfer + 3;
  }
#endif
  else {
    return i2cTransferUsageFault;
  }

  seq = transfer->seq;
  while (!finished) {
    pending = i2c->IF;

    /* If some sort of fault, abort transfer. */
    if (pending & I2C_IF_ERRORS) {
      if (pending & I2C_IF_ARBLOST) {
        /* If an arbitration fault, indicates either a slave device */
        /* not responding as expected, or other master which is not */
        /* supported by this software. */
        transfer->result = i2cTransferArbLost;
      } else if (pending & I2C_IF_BUSERR) {
        /* A bus error indicates a misplaced start or stop, which should */
        /* not occur in master mode controlled by this software. */
        transfer->result = i2cTransferBusErr;
      }

      /* Ifan error occurs, it is difficult to know */
      /* an exact cause and how to resolve. It will be up to a wrapper */
      /* to determine how to handle a fault/recovery if possible. */
      transfer->state = i2cStateDone;
      break;
    }

    switch (transfer->state) {
      /***************************************************/
      /* Send the first start+address (first byte if 10 bit). */
      /***************************************************/
      case i2cStateStartAddrSend:
        if (seq->flags & I2C_FLAG_10BIT_ADDR) {
          tmp = (((uint32_t)(seq->addr) >> 8) & 0x06) | 0xf0;

          /* In 10 bit address mode, the address following the first */
          /* start always indicates write. */
        } else {
          tmp = (uint32_t)(seq->addr) & 0xfe;

          if (seq->flags & I2C_FLAG_READ) {
            /* Indicate read request */
            tmp |= 1;
          }
        }

        transfer->state = i2cStateAddrWFAckNack;
        i2c->TXDATA     = tmp;/* Data not transmitted until the START is sent. */
        i2c->CMD        = I2C_CMD_START;
        finished = true;
        break;

      /*******************************************************/
      /* Wait for ACK/NACK on the address (first byte if 10 bit). */
      /*******************************************************/
      case i2cStateAddrWFAckNack:
        if (pending & I2C_IF_NACK) {
          I2C_IntClear(i2c, I2C_IF_NACK);
          transfer->result = i2cTransferNack;
          transfer->state  = i2cStateWFStopSent;
          i2c->CMD         = I2C_CMD_STOP;
        } else if (pending & I2C_IF_ACK) {
          I2C_IntClear(i2c, I2C_IF_ACK);

          /* If a 10 bit address, send the 2nd byte of the address. */
          if (seq->flags & I2C_FLAG_10BIT_ADDR) {
            transfer->state = i2cStateAddrWF2ndAckNack;
            i2c->TXDATA     = (uint32_t)(seq->addr) & 0xff;
          } else {
            /* Determine whether receiving or sending data. */
            if (seq->flags & I2C_FLAG_READ) {
              transfer->state = i2cStateWFData;
              if (seq->buf[transfer->bufIndx].len == 1) {
                i2c->CMD  = I2C_CMD_NACK;
              }
            } else {
              transfer->state = i2cStateDataSend;
              continue;
            }
          }
        }
        finished = true;
        break;

      /******************************************************/
      /* Wait for ACK/NACK on the second byte of a 10 bit address. */
      /******************************************************/
      case i2cStateAddrWF2ndAckNack:
        if (pending & I2C_IF_NACK) {
          I2C_IntClear(i2c, I2C_IF_NACK);
          transfer->result = i2cTransferNack;
          transfer->state  = i2cStateWFStopSent;
          i2c->CMD         = I2C_CMD_STOP;
        } else if (pending & I2C_IF_ACK) {
          I2C_IntClear(i2c, I2C_IF_ACK);

          /* If using a plain read sequence with a 10 bit address, switch to send */
          /* a repeated start. */
          if (seq->flags & I2C_FLAG_READ) {
            transfer->state = i2cStateRStartAddrSend;
          }
          /* Otherwise, expected to write 0 or more bytes. */
          else {
            transfer->state = i2cStateDataSend;
          }
          continue;
        }
        finished = true;
        break;

      /*******************************/
      /* Send a repeated start+address */
      /*******************************/
      case i2cStateRStartAddrSend:
        if (seq->flags & I2C_FLAG_10BIT_ADDR) {
          tmp = (uint32_t)((seq->addr >> 8) & 0x06) | 0xf0;
        } else {
          tmp = (uint32_t)(seq->addr & 0xfe);
        }

        /* If this is a write+read combined sequence, read is about to start. */
        if (seq->flags & I2C_FLAG_WRITE_READ) {
          /* Indicate a read request. */
          tmp |= 1;
          /* If reading only one byte, prepare the NACK now before START command. */
          if (seq->buf[transfer->bufIndx].len == 1) {
            i2c->CMD = I2C_CMD_NACK;
          }
        }

        transfer->state = i2cStateRAddrWFAckNack;
        /* The START command has to be written first since repeated start. Otherwise, */
        /* data would be sent first. */
        i2c->CMD    = I2C_CMD_START;
        i2c->TXDATA = tmp;

        finished = true;
        break;

      /**********************************************************************/
      /* Wait for ACK/NACK on the repeated start+address (first byte if 10 bit) */
      /**********************************************************************/
      case i2cStateRAddrWFAckNack:
        if (pending & I2C_IF_NACK) {
          I2C_IntClear(i2c, I2C_IF_NACK);
          transfer->result = i2cTransferNack;
          transfer->state  = i2cStateWFStopSent;
          i2c->CMD         = I2C_CMD_STOP;
        } else if (pending & I2C_IF_ACK) {
          I2C_IntClear(i2c, I2C_IF_ACK);

          /* Determine whether receiving or sending data. */
          if (seq->flags & I2C_FLAG_WRITE_READ) {
            transfer->state = i2cStateWFData;
          } else {
            transfer->state = i2cStateDataSend;
            continue;
          }
        }
        finished = true;
        break;

      /*****************************/
      /* Send a data byte to the slave */
      /*****************************/
      case i2cStateDataSend:
        /* Reached end of data buffer. */
        if (transfer->offset >= seq->buf[transfer->bufIndx].len) {
          /* Move to the next message part. */
          transfer->offset = 0;
          transfer->bufIndx++;

          /* Send a repeated start when switching to read mode on the 2nd buffer. */
          if (seq->flags & I2C_FLAG_WRITE_READ) {
            transfer->state = i2cStateRStartAddrSend;
            continue;
          }

          /* Only writing from one buffer or finished both buffers. */
          if ((seq->flags & I2C_FLAG_WRITE) || (transfer->bufIndx > 1)) {
            transfer->state = i2cStateWFStopSent;
            i2c->CMD        = I2C_CMD_STOP;
            finished = true;
            break;
          }

          /* Reprocess in case the next buffer is empty. */
          continue;
        }

        /* Send byte. */
        i2c->TXDATA     = (uint32_t)(seq->buf[transfer->bufIndx].data[transfer->offset++]);
        transfer->state = i2cStateDataWFAckNack;
        finished = true;
        break;

      /*********************************************************/
      /* Wait for ACK/NACK from the slave after sending data to it. */
      /*********************************************************/
      case i2cStateDataWFAckNack:
        if (pending & I2C_IF_NACK) {
          I2C_IntClear(i2c, I2C_IF_NACK);
          transfer->result = i2cTransferNack;
          transfer->state  = i2cStateWFStopSent;
          i2c->CMD         = I2C_CMD_STOP;
        } else if (pending & I2C_IF_ACK) {
          I2C_IntClear(i2c, I2C_IF_ACK);
          transfer->state = i2cStateDataSend;
          continue;
        }
        finished = true;
        break;

      /****************************/
      /* Wait for data from slave */
      /****************************/
      case i2cStateWFData:
        if (pending & I2C_IF_RXDATAV) {
          uint8_t       data;
          unsigned int  rxLen = seq->buf[transfer->bufIndx].len;

          /* Must read out data not to block further progress. */
          data = (uint8_t)(i2c->RXDATA);

#if (defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1)         \
          || defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) \
          || defined(_SILICON_LABS_32B_SERIES_2_CONFIG_3))
          // Errata I2C_E303. I2C Fails to Indicate New Incoming Data.
          uint32_t status = i2c->STATUS;
          // look for invalid RXDATAV = 0 and RXFULL = 1 condition
          if (((status & I2C_IF_RXDATAV) == 0) & ((status & I2C_IF_RXFULL) != 0)) {
            // Performing a dummy read of the RXFIFO (I2C_RXDATA).
            // This restores the expected RXDATAV = 1 and RXFULL = 0 condition.
            (void)i2c->RXDATA;
            // The dummy read will also set the RXUFIF flag bit, which should be ignored and cleared.
            I2C_IntClear(i2c, I2C_IF_RXUF);
          }
#endif

          /* SW needs to clear RXDATAV IF on Series 2 devices.
             Flag is kept high by HW if buffer is not empty. */
#if defined(_SILICON_LABS_32B_SERIES_2)
          I2C_IntClear(i2c, I2C_IF_RXDATAV);
#endif

          /* Make sure that there is no storing beyond the end of the buffer (just in case). */
          if (transfer->offset < rxLen) {
            seq->buf[transfer->bufIndx].data[transfer->offset++] = data;
          }

          /* If all requested data is read, the sequence should end. */
          if (transfer->offset >= rxLen) {
            transfer->state = i2cStateWFStopSent;
            i2c->CMD        = I2C_CMD_STOP;
          } else {
            /* Send ACK and wait for the next byte. */
            i2c->CMD = I2C_CMD_ACK;

            if ( (1 < rxLen) && (transfer->offset == (rxLen - 1)) ) {
              /* If receiving more than one byte and this is the next
                 to last byte, transmit the NACK now before receiving
                 the last byte. */
              i2c->CMD  = I2C_CMD_NACK;
            }
          }
        }
        finished = true;
        break;

      /***********************************/
      /* Wait for STOP to have been sent */
      /***********************************/
      case i2cStateWFStopSent:
        if (pending & I2C_IF_MSTOP) {
          I2C_IntClear(i2c, I2C_IF_MSTOP);
          transfer->state = i2cStateDone;
        }
        finished = true;
        break;

      /******************************/
      /* An unexpected state, software fault */
      /******************************/
      default:
        transfer->result = i2cTransferSwFault;
        transfer->state  = i2cStateDone;
        finished = true;
        break;
    }
  }

  if (transfer->state == i2cStateDone) {
    /* Disable interrupt sources when done. */
    i2c->IEN = 0;

    /* Update the result unless a fault has already occurred. */
    if (transfer->result == i2cTransferInProgress) {
      transfer->result = i2cTransferDone;
    }
  }
  /* Until transfer is done, keep returning i2cTransferInProgress. */
  else {
    return i2cTransferInProgress;
  }

  return transfer->result;
}

/***************************************************************************//**
 * @brief
 *   Prepare and start an I2C transfer (single master mode only).
 *
 * @details
 *   This function must be invoked to start an I2C transfer
 *   sequence. To complete the transfer, I2C_Transfer() must
 *   be used either in polled mode or by adding a small driver wrapper using
 *   interrupts.
 *
 * @note
 *   Only single master mode is supported.
 *
 * @param[in] i2c
 *   A pointer to the I2C peripheral register block.
 *
 * @param[in] seq
 *   A pointer to the sequence structure defining the I2C transfer to take place. The
 *   referenced structure must exist until the transfer has fully completed.
 *
 * @return
 *   Returns the status for an ongoing transfer:
 *   @li #i2cTransferInProgress - indicates that the transfer is not finished.
 *   @li Otherwise, an error has occurred.
 ******************************************************************************/
I2C_TransferReturn_TypeDef I2C_TransferInit(I2C_TypeDef *i2c,
                                            I2C_TransferSeq_TypeDef *seq)
{
  I2C_Transfer_TypeDef *transfer;

  EFM_ASSERT(I2C_REF_VALID(i2c));
  EFM_ASSERT(seq);

  /* Support up to 2 I2C buses. */
  if (i2c == I2C0) {
    transfer = i2cTransfer;
  }
#if (I2C_COUNT > 1)
  else if (i2c == I2C1) {
    transfer = i2cTransfer + 1;
  }
#endif
#if (I2C_COUNT > 2)
  else if (i2c == I2C2) {
    transfer = i2cTransfer + 2;
  }
#endif
#if (I2C_COUNT > 3)
  else if (i2c == I2C3) {
    transfer = i2cTransfer + 3;
  }
#endif
  else {
    return i2cTransferUsageFault;
  }

  /* Check if in a busy state. Since this software assumes a single master, */
  /* issue an abort. The BUSY state is normal after a reset. */
  if (i2c->STATE & I2C_STATE_BUSY) {
    i2c->CMD = I2C_CMD_ABORT;
  }

  /* Do not try to read 0 bytes. It is not */
  /* possible according to the I2C spec, since the slave will always start */
  /* sending the first byte ACK on an address. The read operation can */
  /* only be stopped by NACKing a received byte, i.e., minimum 1 byte. */
  if (((seq->flags & I2C_FLAG_READ) && !(seq->buf[0].len))
      || ((seq->flags & I2C_FLAG_WRITE_READ) && !(seq->buf[1].len))
      ) {
    return i2cTransferUsageFault;
  }

  /* Prepare for a transfer. */
  transfer->state   = i2cStateStartAddrSend;
  transfer->result  = i2cTransferInProgress;
  transfer->offset  = 0;
  transfer->bufIndx = 0;
  transfer->seq     = seq;

  /* Ensure buffers are empty. */
  i2c->CMD = I2C_CMD_CLEARPC | I2C_CMD_CLEARTX;
  flushRx(i2c);

  /* Clear all pending interrupts prior to starting a transfer. */
  I2C_IntClear(i2c, _I2C_IF_MASK);

  /* Enable relevant interrupts. */
  /* Notice that the I2C interrupt must also be enabled in the NVIC, but */
  /* that is left for an additional driver wrapper. */
  i2c->IEN |= I2C_IEN_NACK | I2C_IEN_ACK | I2C_IEN_MSTOP
              | I2C_IEN_RXDATAV | I2C_IEN_ERRORS;

  /* Start a transfer. */
  return I2C_Transfer(i2c);
}

/** @} (end addtogroup i2c) */
#endif /* defined(I2C_COUNT) && (I2C_COUNT > 0) */
