/***************************************************************************//** * @file * @brief Inter-integrated Circuit (I2C) Peripheral API ******************************************************************************* * # License * Copyright 2018 Silicon Laboratories Inc. www.silabs.com ******************************************************************************* * * 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 "em_assert.h" #include /***************************************************************************//** * @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)) #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) #define I2C_CR_MAX 4 #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 = ((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; 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 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 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; goto done; } 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; goto done; /*******************************************************/ /* 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; } } } goto done; /******************************************************/ /* 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; } goto done; /*******************************/ /* Send a 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, 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; goto done; /**********************************************************************/ /* 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; } } goto done; /*****************************/ /* 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; goto done; } /* 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; goto done; /*********************************************************/ /* 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; } 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 not to block further progress. */ data = (uint8_t)(i2c->RXDATA); /* 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; } } } goto done; /***********************************/ /* Wait for STOP to have been sent */ /***********************************/ case i2cStateWFStopSent: if (pending & I2C_IF_MSTOP) { I2C_IntClear(i2c, I2C_IF_MSTOP); transfer->state = i2cStateDone; } goto done; /******************************/ /* An unexpected state, software fault */ /******************************/ default: transfer->result = i2cTransferSwFault; transfer->state = i2cStateDone; goto done; } } done: 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 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) */