/***************************************************************************//**
 * @file em_leuart.c
 * @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART)
 *   Peripheral API
 * @version 3.20.12
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2014 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_leuart.h"
#if defined(LEUART_COUNT) && (LEUART_COUNT > 0)

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

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

/***************************************************************************//**
 * @addtogroup LEUART
 * @brief Low Energy Universal Asynchronous Receiver/Transmitter (LEUART)
 *        Peripheral API
 * @{
 ******************************************************************************/

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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */


/** Validation of LEUART register block pointer reference
 *  for assert statements. */
#if (LEUART_COUNT == 1)
#define LEUART_REF_VALID(ref)    ((ref) == LEUART0)
#elif (LEUART_COUNT == 2)
#define LEUART_REF_VALID(ref)    (((ref) == LEUART0) || ((ref) == LEUART1))
#else
#error Undefined number of low energy UARTs (LEUART).
#endif

/** @endcond */

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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */

/***************************************************************************//**
 * @brief
 *   Wait for ongoing sync of register(s) to low frequency domain to complete.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block
 *
 * @param[in] mask
 *   Bitmask corresponding to SYNCBUSY register defined bits, indicating
 *   registers that must complete any ongoing synchronization.
 ******************************************************************************/
__STATIC_INLINE void LEUART_Sync(LEUART_TypeDef *leuart, uint32_t mask)
{
  /* Avoid deadlock if modifying the same register twice when freeze mode is */
  /* activated. */
  if (leuart->FREEZE & LEUART_FREEZE_REGFREEZE)
  {
    return;
  }

  /* Wait for any pending previous write operation to have been completed */
  /* in low frequency domain */
  while (leuart->SYNCBUSY & mask)
    ;
}

/** @endcond */

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

/***************************************************************************//**
 * @brief
 *   Calculate baudrate for LEUART given reference frequency and clock division.
 *
 * @details
 *   This function returns the baudrate that a LEUART module will use if
 *   configured with the given frequency and clock divisor. Notice that
 *   this function will not use actual HW configuration. It can be used
 *   to determinate if a given configuration is sufficiently accurate for the
 *   application.
 *
 * @param[in] refFreq
 *   LEUART peripheral frequency used.
 *
 * @param[in] clkdiv
 *   Clock division factor to be used.
 *
 * @return
 *   Baudrate with given settings.
 ******************************************************************************/
uint32_t LEUART_BaudrateCalc(uint32_t refFreq, uint32_t clkdiv)
{
  uint32_t divisor;
  uint32_t remainder;
  uint32_t quotient;
  uint32_t br;

  /* Mask out unused bits */
  clkdiv &= _LEUART_CLKDIV_MASK;

  /* We want to use integer division to avoid forcing in float division */
  /* utils, and yet keep rounding effect errors to a minimum. */

  /*
   * Baudrate is given by:
   *
   * br = fLEUARTn/(1 + (CLKDIV / 256))
   *
   * which can be rewritten to
   *
   * br = (256 * fLEUARTn)/(256 + CLKDIV)
   *
   * Normally, with fLEUARTn appr 32768Hz, there is no problem with overflow
   * if using 32 bit arithmetic. However, since fLEUARTn may be derived from
   * HFCORECLK as well, we must consider overflow when using integer arithmetic.
   */

  /*
   * The basic problem with integer division in the above formula is that
   * the dividend (256 * fLEUARTn) may become higher than max 32 bit
   * integer. Yet we want to evaluate dividend first before dividing in
   * order to get as small rounding effects as possible. We do not want
   * to make too harsh restrictions on max fLEUARTn value either.
   *
   * For division a/b, we can write
   *
   * a = qb + r
   *
   * where q is the quotient and r is the remainder, both integers.
   *
   * The orignal baudrate formula can be rewritten as
   *
   * br = 256a / b = 256(qb + r)/b = 256q + 256r/b
   *
   * where a is 'refFreq' and b is 'divisor', referring to variable names.
   */

  divisor = 256 + clkdiv;

  quotient  = refFreq / divisor;
  remainder = refFreq % divisor;

  /* Since divisor >= 256, the below cannot exceed max 32 bit value. */
  br = 256 * quotient;

  /*
   * Remainder < (256 + clkdiv), which means dividend (256 * remainder) worst case is
   * 256*(256 + 0x7ff8) = 0x80F800.
   */
  br += (256 * remainder) / divisor;

  return br;
}


/***************************************************************************//**
 * @brief
 *   Get current baudrate for LEUART.
 *
 * @details
 *   This function returns the actual baudrate (not considering oscillator
 *   inaccuracies) used by a LEUART peripheral.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @return
 *   Current baudrate.
 ******************************************************************************/
uint32_t LEUART_BaudrateGet(LEUART_TypeDef *leuart)
{
  uint32_t          freq;
  CMU_Clock_TypeDef clock;

  /* Get current frequency */
  if (leuart == LEUART0)
  {
    clock = cmuClock_LEUART0;
  }
#if (LEUART_COUNT > 1)
  else if (leuart == LEUART1)
  {
    clock = cmuClock_LEUART1;
  }
#endif
  else
  {
    EFM_ASSERT(0);
    return 0;
  }

  freq = CMU_ClockFreqGet(clock);

  return LEUART_BaudrateCalc(freq, leuart->CLKDIV);
}


/***************************************************************************//**
 * @brief
 *   Configure baudrate (or as close as possible to specified baudrate).
 *
 * @note
 *   The setting of a baudrate requires synchronization into the
 *   low frequency domain. If the same register is modified before a previous
 *   update has completed, this function will stall until the previous
 *   synchronization has completed.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] refFreq
 *   LEUART reference clock frequency in Hz that will be used. If set to 0,
 *   the currently configured reference clock is assumed.
 *
 * @param[in] baudrate
 *   Baudrate to try to achieve for LEUART.
 ******************************************************************************/
void LEUART_BaudrateSet(LEUART_TypeDef *leuart,
                        uint32_t refFreq,
                        uint32_t baudrate)
{
  uint32_t          clkdiv;
  CMU_Clock_TypeDef clock;

  /* Inhibit divide by 0 */
  EFM_ASSERT(baudrate);

  /*
   * We want to use integer division to avoid forcing in float division
   * utils, and yet keep rounding effect errors to a minimum.
   *
   * CLKDIV in asynchronous mode is given by:
   *
   * CLKDIV = 256*(fLEUARTn/br - 1) = ((256*fLEUARTn)/br) - 256
   *
   * Normally, with fLEUARTn appr 32768Hz, there is no problem with overflow
   * if using 32 bit arithmetic. However, since fLEUARTn may be derived from
   * HFCORECLK as well, we must consider overflow when using integer arithmetic.
   *
   * The basic problem with integer division in the above formula is that
   * the dividend (256 * fLEUARTn) may become higher than max 32 bit
   * integer. Yet, we want to evaluate dividend first before dividing in
   * order to get as small rounding effects as possible. We do not want
   * to make too harsh restrictions on max fLEUARTn value either.
   *
   * Since the last 3 bits of CLKDIV are don't care, we can base our
   * integer arithmetic on the below formula
   *
   * CLKDIV/8 = ((32*fLEUARTn)/br) - 32
   *
   * and calculate 1/8 of CLKDIV first. This allows for fLEUARTn
   * up to 128MHz without overflowing a 32 bit value!
   */

  /* Get current frequency? */
  if (!refFreq)
  {
    if (leuart == LEUART0)
    {
      clock = cmuClock_LEUART0;
    }
#if (LEUART_COUNT > 1)
    else if (leuart == LEUART1)
    {
      clock = cmuClock_LEUART1;
    }
#endif
    else
    {
      EFM_ASSERT(0);
      return;
    }

    refFreq = CMU_ClockFreqGet(clock);
  }

  /* Calculate and set CLKDIV with fractional bits */
  clkdiv  = (32 * refFreq) / baudrate;
  clkdiv -= 32;
  clkdiv *= 8;

  /* Verify that resulting clock divider is within limits */
  EFM_ASSERT(clkdiv <= _LEUART_CLKDIV_MASK);

  /* If EFM_ASSERT is not enabled, make sure we don't write to reserved bits */
  clkdiv &= _LEUART_CLKDIV_MASK;

  /* LF register about to be modified require sync. busy check */
  LEUART_Sync(leuart, LEUART_SYNCBUSY_CLKDIV);

  leuart->CLKDIV = clkdiv;
}


/***************************************************************************//**
 * @brief
 *   Enable/disable LEUART receiver and/or transmitter.
 *
 * @details
 *   Notice that this function does not do any configuration. Enabling should
 *   normally be done after initialization is done (if not enabled as part
 *   of init).
 *
 * @note
 *   Enabling/disabling requires synchronization into the low frequency domain.
 *   If the same register is modified before a previous update has completed,
 *   this function will stall until the previous synchronization has completed.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] enable
 *   Select status for receiver/transmitter.
 ******************************************************************************/
void LEUART_Enable(LEUART_TypeDef *leuart, LEUART_Enable_TypeDef enable)
{
  uint32_t tmp;

  /* Make sure the module exists on the selected chip */
  EFM_ASSERT(LEUART_REF_VALID(leuart));

  /* Disable as specified */
  tmp   = ~((uint32_t)(enable));
  tmp  &= (_LEUART_CMD_RXEN_MASK | _LEUART_CMD_TXEN_MASK);
  tmp <<= 1;
  /* Enable as specified */
  tmp |= (uint32_t)(enable);

  /* LF register about to be modified require sync. busy check */
  LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD);

  leuart->CMD = tmp;
}


/***************************************************************************//**
 * @brief
 *   LEUART register synchronization freeze control.
 *
 * @details
 *   Some LEUART registers require synchronization into the low frequency (LF)
 *   domain. The freeze feature allows for several such registers to be
 *   modified before passing them to the LF domain simultaneously (which
 *   takes place when the freeze mode is disabled).
 *
 * @note
 *   When enabling freeze mode, this function will wait for all current
 *   ongoing LEUART synchronization to LF domain to complete (Normally
 *   synchronization will not be in progress.) However for this reason, when
 *   using freeze mode, modifications of registers requiring LF synchronization
 *   should be done within one freeze enable/disable block to avoid unecessary
 *   stalling.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] enable
 *   @li true - enable freeze, modified registers are not propagated to the
 *       LF domain
 *   @li false - disables freeze, modified registers are propagated to LF
 *       domain
 ******************************************************************************/
void LEUART_FreezeEnable(LEUART_TypeDef *leuart, bool enable)
{
  if (enable)
  {
    /*
     * Wait for any ongoing LF synchronization to complete. This is just to
     * protect against the rare case when a user
     * - modifies a register requiring LF sync
     * - then enables freeze before LF sync completed
     * - then modifies the same register again
     * since modifying a register while it is in sync progress should be
     * avoided.
     */
    while (leuart->SYNCBUSY)
      ;

    leuart->FREEZE = LEUART_FREEZE_REGFREEZE;
  }
  else
  {
    leuart->FREEZE = 0;
  }
}


/***************************************************************************//**
 * @brief
 *   Init LEUART.
 *
 * @details
 *   This function will configure basic settings in order to operate in normal
 *   asynchronous mode. Consider using LEUART_Reset() prior to this function if
 *   state of configuration is not known, since only configuration settings
 *   specified by @p init are set.
 *
 *   Special control setup not covered by this function may be done either
 *   before or after using this function (but normally before enabling)
 *   by direct modification of the CTRL register.
 *
 *   Notice that pins used by the LEUART module must be properly configured
 *   by the user explicitly, in order for the LEUART to work as intended.
 *   (When configuring pins, one should remember to consider the sequence of
 *   configuration, in order to avoid unintended pulses/glitches on output
 *   pins.)
 *
 * @note
 *   Initializing requires synchronization into the low frequency domain.
 *   If the same register is modified before a previous update has completed,
 *   this function will stall until the previous synchronization has completed.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] init
 *   Pointer to initialization structure used to configure basic async setup.
 ******************************************************************************/
void LEUART_Init(LEUART_TypeDef *leuart, LEUART_Init_TypeDef const *init)
{
  /* Make sure the module exists on the selected chip */
  EFM_ASSERT(LEUART_REF_VALID(leuart));

  /* LF register about to be modified require sync. busy check */
  LEUART_Sync(leuart, LEUART_SYNCBUSY_CMD);

  /* Ensure disabled while doing config */
  leuart->CMD = LEUART_CMD_RXDIS | LEUART_CMD_TXDIS;

  /* Freeze registers to avoid stalling for LF synchronization */
  LEUART_FreezeEnable(leuart, true);

  /* Configure databits and stopbits */
  leuart->CTRL = (leuart->CTRL & ~(_LEUART_CTRL_PARITY_MASK |
                                   _LEUART_CTRL_STOPBITS_MASK)) |
                 (uint32_t)(init->databits) |
                 (uint32_t)(init->parity) |
                 (uint32_t)(init->stopbits);

  /* Configure baudrate */
  LEUART_BaudrateSet(leuart, init->refFreq, init->baudrate);

  /* Finally enable (as specified) */
  leuart->CMD = (uint32_t)(init->enable);

  /* Unfreeze registers, pass new settings on to LEUART */
  LEUART_FreezeEnable(leuart, false);
}


/***************************************************************************//**
 * @brief
 *   Reset LEUART to same state as after a HW reset.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 ******************************************************************************/
void LEUART_Reset(LEUART_TypeDef *leuart)
{
  /* Make sure the module exists on the selected chip */
  EFM_ASSERT(LEUART_REF_VALID(leuart));

  /* Freeze registers to avoid stalling for LF synchronization */
  LEUART_FreezeEnable(leuart, true);

  /* Make sure disabled first, before resetting other registers */
  leuart->CMD = LEUART_CMD_RXDIS | LEUART_CMD_TXDIS | LEUART_CMD_RXBLOCKDIS |
                LEUART_CMD_CLEARTX | LEUART_CMD_CLEARRX;
  leuart->CTRL       = _LEUART_CTRL_RESETVALUE;
  leuart->CLKDIV     = _LEUART_CLKDIV_RESETVALUE;
  leuart->STARTFRAME = _LEUART_STARTFRAME_RESETVALUE;
  leuart->SIGFRAME   = _LEUART_SIGFRAME_RESETVALUE;
  leuart->IEN        = _LEUART_IEN_RESETVALUE;
  leuart->IFC        = _LEUART_IFC_MASK;
  leuart->PULSECTRL  = _LEUART_PULSECTRL_RESETVALUE;
  leuart->ROUTE      = _LEUART_ROUTE_RESETVALUE;

  /* Unfreeze registers, pass new settings on to LEUART */
  LEUART_FreezeEnable(leuart, false);
}


/***************************************************************************//**
 * @brief
 *   Receive one 8 bit frame, (or part of 9 bit frame).
 *
 * @details
 *   This function is normally used to receive one frame when operating with
 *   frame length 8 bits. Please refer to LEUART_RxExt() for reception of
 *   9 bit frames.
 *
 *   Notice that possible parity/stop bits are not considered part of specified
 *   frame bit length.
 *
 * @note
 *   This function will stall if buffer is empty, until data is received.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @return
 *   Data received.
 ******************************************************************************/
uint8_t LEUART_Rx(LEUART_TypeDef *leuart)
{
  while (!(leuart->STATUS & LEUART_STATUS_RXDATAV))
    ;

  return (uint8_t)(leuart->RXDATA);
}


/***************************************************************************//**
 * @brief
 *   Receive one 8-9 bit frame, with extended information.
 *
 * @details
 *   This function is normally used to receive one frame and additional RX
 *   status information is required.
 *
 * @note
 *   This function will stall if buffer is empty, until data is received.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @return
 *   Data received.
 ******************************************************************************/
uint16_t LEUART_RxExt(LEUART_TypeDef *leuart)
{
  while (!(leuart->STATUS & LEUART_STATUS_RXDATAV))
    ;

  return (uint16_t)(leuart->RXDATAX);
}


/***************************************************************************//**
 * @brief
 *   Transmit one frame.
 *
 * @details
 *   Depending on frame length configuration, 8 (least significant) bits from
 *   @p data are transmitted. If frame length is 9, 8 bits are transmitted from
 *   @p data and one bit as specified by CTRL register, BIT8DV field. Please
 *   refer to LEUART_TxExt() for transmitting 9 bit frame with full control of
 *   all 9 bits.
 *
 *   Notice that possible parity/stop bits in asynchronous mode are not
 *   considered part of specified frame bit length.
 *
 * @note
 *   This function will stall if buffer is full, until buffer becomes available.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] data
 *   Data to transmit. See details above for further info.
 ******************************************************************************/
void LEUART_Tx(LEUART_TypeDef *leuart, uint8_t data)
{
  /* Check that transmit buffer is empty */
  while (!(leuart->STATUS & LEUART_STATUS_TXBL))
    ;

  /* LF register about to be modified require sync. busy check */
  LEUART_Sync(leuart, LEUART_SYNCBUSY_TXDATA);

  leuart->TXDATA = (uint32_t)data;
}


/***************************************************************************//**
 * @brief
 *   Transmit one 8-9 bit frame with extended control.
 *
 * @details
 *   Notice that possible parity/stop bits in asynchronous mode are not
 *   considered part of specified frame bit length.
 *
 * @note
 *   This function will stall if buffer is full, until buffer becomes available.
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] data
 *   Data to transmit with extended control. Least significant bits contains
 *   frame bits, and additional control bits are available as documented in
 *   the EFM32 reference manual (set to 0 if not used).
 ******************************************************************************/
void LEUART_TxExt(LEUART_TypeDef *leuart, uint16_t data)
{
  /* Check that transmit buffer is empty */
  while (!(leuart->STATUS & LEUART_STATUS_TXBL))
    ;

  /* LF register about to be modified require sync. busy check */
  LEUART_Sync(leuart, LEUART_SYNCBUSY_TXDATAX);

  leuart->TXDATAX = (uint32_t)data;
}

/***************************************************************************//**
 * @brief
 *   Enables handling of LEUART TX by DMA in EM2
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] enable
 *   true - enables functionality
 *   false - disables functionality
 *
 ******************************************************************************/
void LEUART_TxDmaInEM2Enable(LEUART_TypeDef *leuart, bool enable)
{
  /* LF register about to be modified require sync. busy check */
  LEUART_Sync(leuart, LEUART_SYNCBUSY_CTRL);

  if (enable)
  {
    leuart->CTRL |= LEUART_CTRL_TXDMAWU;
  }
  else
  {
    leuart->CTRL &= ~LEUART_CTRL_TXDMAWU;
  }
}

/***************************************************************************//**
 * @brief
 *   Enables handling of LEUART RX by DMA in EM2
 *
 * @param[in] leuart
 *   Pointer to LEUART peripheral register block.
 *
 * @param[in] enable
 *   true - enables functionality
 *   false - disables functionality
 *
 ******************************************************************************/
void LEUART_RxDmaInEM2Enable(LEUART_TypeDef *leuart, bool enable)
{
  /* LF register about to be modified require sync. busy check */
  LEUART_Sync(leuart, LEUART_SYNCBUSY_CTRL);

  if (enable)
  {
    leuart->CTRL |= LEUART_CTRL_RXDMAWU;
  }
  else
  {
    leuart->CTRL &= ~LEUART_CTRL_RXDMAWU;
  }
}


/** @} (end addtogroup LEUART) */
/** @} (end addtogroup EM_Library) */
#endif /* defined(LEUART_COUNT) && (LEUART_COUNT > 0) */
