Newer
Older
mbed-os / hal / targets / hal / TARGET_Silicon_Labs / TARGET_EFM32 / emlib / src / em_i2c.c
@Mihail Stoyanov Mihail Stoyanov on 23 May 2016 27 KB Simplify layout:
/***************************************************************************//**
 * @file em_i2c.c
 * @brief Inter-integrated Circuit (I2C) Peripheral API
 * @version 4.2.1
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2015 Silicon Labs, http://www.silabs.com</b>
 *******************************************************************************
 *
 * 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.
 * 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.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Silicon Labs has no
 * obligation to support this Software. Silicon Labs is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Silicon Labs will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 ******************************************************************************/

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

#include "em_cmu.h"
#include "em_bus.h"
#include "em_assert.h"

 #include <limits.h>

/***************************************************************************//**
 * @addtogroup EM_Library
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup I2C
 * @brief Inter-integrated Circuit (I2C) Peripheral API
 * @{
 ******************************************************************************/

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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/** Validation of 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))
#endif

/** Error flags indicating I2C transfer has failed somehow. */
/* Notice that I2C_IF_TXOF (transmit overflow) is not really possible with */
/* this SW supporting master mode. Likewise for I2C_IF_RXUF (receive underflow) */
/* RXUF is only likely to occur with this SW if using a debugger peeking into */
/* RXDATA register. Thus, we ignore those types of fault. */
#define I2C_IF_ERRORS    (I2C_IF_BUSERR | I2C_IF_ARBLOST)

/* Max I2C transmission rate constant  */
#if defined( _SILICON_LABS_32B_PLATFORM_1 )
#define I2C_CR_MAX       4
#elif defined( _SILICON_LABS_32B_PLATFORM_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 (first part of) address. */
  i2cStateAddrWF2ndAckNack,    /**< Wait for ACK/NACK on second part of 10 bit address. */
  i2cStateRStartAddrSend,      /**< Send repeated start + (first part of) address. */
  i2cStateRAddrWFAckNack,      /**< Wait for ACK/NACK on address sent after 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 current sequence buffer. */
  uint16_t                   offset;

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

  /** Reference to I2C transfer sequence definition provided by 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 undefined
 * index (0x3) to reflect default setting just in case.
 */
static const uint8_t i2cNSum[] = { 4 + 4, 6 + 3, 11 + 6, 4 + 4 };

/** Transfer state info for ongoing master mode transfer */
static I2C_Transfer_TypeDef i2cTransfer[I2C_COUNT];

/** @endcond */

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

/***************************************************************************//**
 * @brief
 *   Get current configured I2C bus frequency.
 *
 * @details
 *   This frequency is only of relevance when acting as master.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 *
 * @return
 *   Current I2C frequency in Hz.
 ******************************************************************************/
uint32_t I2C_BusFreqGet(I2C_TypeDef *i2c)
{
  uint32_t freqHfper;
  uint32_t n;

  /* Max frequency is given by freqScl = freqHfper/((Nlow + Nhigh)(DIV + 1) + I2C_CR_MAX)
   * More details can be found in the reference manual,
   * I2C Clock Generation chapter. */
  freqHfper = CMU_ClockFreqGet(cmuClock_HFPER);
  /* 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 I2C bus frequency.
 *
 * @details
 *   The bus frequency is only of relevance when acting as a master. The bus
 *   frequency should not be set higher than the max frequency accepted by the
 *   slowest device on the bus.
 *
 *   Notice that due to asymmetric requirements on low and high I2C clock
 *   cycles by the I2C specification, the actual max frequency allowed in order
 *   to comply with the specification may be somewhat lower than expected.
 *
 *   Please refer to the reference manual, details on I2C clock generation,
 *   for max allowed theoretical frequencies for different modes.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 *
 * @param[in] freqRef
 *   I2C reference clock frequency in Hz that will be used. If set to 0,
 *   then HFPER clock is used. Setting it to a higher than actual configured
 *   value only has the consequence of reducing the real I2C frequency.
 *
 * @param[in] freqScl
 *   Bus frequency to set (actual bus speed may be lower due to integer
 *   prescaling). Safe (according to I2C specification) max frequencies for
 *   standard, fast and fast+ modes are available using I2C_FREQ_ defines.
 *   (Using I2C_FREQ_ defines requires corresponding setting of @p type.)
 *   Slowest slave device on bus must always be considered.
 *
 * @param[in] i2cMode
 *   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 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;
  int32_t div;

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

  /* 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)
  {
    freqRef = CMU_ClockFreqGet(cmuClock_HFPER);
  }

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

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

  /* SCL frequency is given by
   * freqScl = freqRef/((Nlow + Nhigh) * (DIV + 1) + I2C_C_MAX)
   *
   * Thus
   * DIV = ((freqRef - (I2C_C_MAX * freqScl))/((Nlow + Nhigh) * freqScl)) - 1
   *
   * More details can be found in the reference manual,
   * I2C Clock Generation chapter.  */

  /* n = Nlow + Nhigh */
  n = (uint32_t)(i2cNSum[i2cMode]);
  div = ((freqRef - (I2C_CR_MAX * freqScl)) / (n * freqScl)) - 1;
  EFM_ASSERT(div >= 0);
  EFM_ASSERT((uint32_t)div <= _I2C_CLKDIV_DIV_MASK);

  /* Clock divisor must be at least 1 in slave mode according to reference */
  /* manual (in which case there is normally no need to set bus frequency). */
  if ((i2c->CTRL & I2C_CTRL_SLAVE) && !div)
  {
    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
 *   Pointer to 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));

  BUS_RegBitWrite(&(i2c->CTRL), _I2C_CTRL_EN_SHIFT, enable);
}


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

  i2c->IEN = 0;
  i2c->IFC = _I2C_IFC_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);

  BUS_RegBitWrite(&(i2c->CTRL), _I2C_CTRL_EN_SHIFT, init->enable);
}


/***************************************************************************//**
 * @brief
 *   Reset I2C to same state as after a HW reset.
 *
 * @note
 *   The ROUTE register is NOT reset by this function, in order to allow for
 *   centralized setup of this feature.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 ******************************************************************************/
void I2C_Reset(I2C_TypeDef *i2c)
{
  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;
  i2c->IFC       = _I2C_IFC_MASK;
  /* Do not reset 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() in order to
 *   complete a transfer. It may be used in polled mode as the below example
 *   shows:
 * @verbatim
 * I2C_TransferReturn_TypeDef ret;
 *
 * // Do a polled transfer
 * ret = I2C_TransferInit(I2C0, seq);
 * while (ret == i2cTransferInProgress)
 * {
 *   ret = I2C_Transfer(I2C0);
 * }
 * @endverbatim
 *  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 SW.
 *
 * @note
 *   Only single master mode is supported.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 *
 * @return
 *   Returns status for 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;

  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
  else
  {
    return i2cTransferUsageFault;
  }

  seq = transfer->seq;
  for (;; )
  {
    pending = i2c->IF;

    /* If some sort of fault, abort transfer. */
    if (pending & I2C_IF_ERRORS)
    {
      if (pending & I2C_IF_ARBLOST)
      {
        /* If arbitration fault, it indicates either a slave device */
        /* not responding as expected, or other master which is not */
        /* supported by this SW. */
        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 SW. */
        transfer->result = i2cTransferBusErr;
      }

      /* If error situation occurred, it is difficult to know */
      /* 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;
      goto done;
    }

    switch (transfer->state)
    {
    /***************************************************/
    /* Send 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 indicate 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 START sent */
      i2c->CMD        = I2C_CMD_START;
      goto done;

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

        /* If 10 bit address, send 2nd byte of 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;
          }
        }
      }
      goto done;

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

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

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

      /* If this is a write+read combined sequence, then read is about to start */
      if (seq->flags & I2C_FLAG_WRITE_READ)
      {
        /* Indicate read request */
        tmp |= 1;
      }

      transfer->state = i2cStateRAddrWFAckNack;
      /* We have to write START cmd first since repeated start, otherwise */
      /* data would be sent first. */
      i2c->CMD    = I2C_CMD_START;
      i2c->TXDATA = tmp;
      goto done;

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

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

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

        /* Send repeated start when switching to read mode on 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;
          goto done;
        }

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

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

    /*********************************************************/
    /* Wait for ACK/NACK from slave after sending data to it */
    /*********************************************************/
    case i2cStateDataWFAckNack:
      if (pending & I2C_IF_NACK)
      {
        i2c->IFC         = I2C_IFC_NACK;
        transfer->result = i2cTransferNack;
        transfer->state  = i2cStateWFStopSent;
        i2c->CMD         = I2C_CMD_STOP;
      }
      else if (pending & I2C_IF_ACK)
      {
        i2c->IFC        = I2C_IFC_ACK;
        transfer->state = i2cStateDataSend;
        continue;
      }
      goto done;

    /****************************/
    /* 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 in order to not block further progress */
        data = (uint8_t)(i2c->RXDATA);

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

        /* If we have read all requested data, then the sequence should end */
        if (transfer->offset >= rxLen)
        {
          /* If there is only one byte to receive we need to transmit the
             NACK now, before the stop. */
          if (1 == rxLen)
          {
            i2c->CMD  = I2C_CMD_NACK;
          }

          transfer->state = i2cStateWFStopSent;
          i2c->CMD        = I2C_CMD_STOP;
        }
        else
        {
          /* Send ACK and wait for next byte */
          i2c->CMD = I2C_CMD_ACK;

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

    /***********************************/
    /* Wait for STOP to have been sent */
    /***********************************/
    case i2cStateWFStopSent:
      if (pending & I2C_IF_MSTOP)
      {
        i2c->IFC        = I2C_IFC_MSTOP;
        transfer->state = i2cStateDone;
      }
      goto done;

    /******************************/
    /* Unexpected state, SW fault */
    /******************************/
    default:
      transfer->result = i2cTransferSwFault;
      transfer->state  = i2cStateDone;
      goto done;
    }
  }

 done:

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

    /* Update result unless some fault 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 in order to start an I2C transfer
 *   sequence. In order to actually complete the transfer, I2C_Transfer() must
 *   be used either in polled mode or by adding a small driver wrapper utilizing
 *   interrupts.
 *
 * @note
 *   Only single master mode is supported.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 *
 * @param[in] seq
 *   Pointer to sequence structure defining the I2C transfer to take place. The
 *   referenced structure must exist until the transfer has fully completed.
 *
 * @return
 *   Returns status for ongoing transfer:
 *   @li #i2cTransferInProgress - indicates that transfer not finished.
 *   @li otherwise some sort of 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
  else
  {
    return i2cTransferUsageFault;
  }

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

  /* Make sure user is not trying to read 0 bytes, it is not */
  /* possible according to I2C spec, since slave will always start */
  /* sending first byte ACK on address. The read operation can */
  /* only be stopped by NACKing a received byte, ie 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;
  if (i2c->IF & I2C_IF_RXDATAV)
  {
    (void)i2c->RXDATA;
  }

  /* Clear all pending interrupts prior to starting transfer. */
  i2c->IFC = _I2C_IFC_MASK;

  /* Enable those interrupts we are interested in throughout transfer. */
  /* Notice that the I2C interrupt must also be enabled in the NVIC, but */
  /* that is left for an additional driver wrapper. */
  i2c->IEN = I2C_IF_NACK | I2C_IF_ACK | I2C_IF_MSTOP |
             I2C_IF_RXDATAV | I2C_IF_ERRORS;

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

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