/***************************************************************************//**
 * @file
 * @brief Timer/counter (TIMER) Peripheral API
 * @version 5.8.4
 *******************************************************************************
 * # 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_timer.h"
#if defined(TIMER_COUNT) && (TIMER_COUNT > 0)

#include "em_assert.h"

/***************************************************************************//**
 * @addtogroup emlib
 * @{
 ******************************************************************************/

/***************************************************************************//**
 * @addtogroup TIMER
 * @brief Timer/Counter (TIMER) Peripheral API
 * @details
 *   The timer module consists of three main parts:
 *   @li General timer configuration and enable control.
 *   @li Compare/capture control.
 *   @li Dead time insertion control (may not be available for all timers).
 * @{
 ******************************************************************************/

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

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
#if defined(_PRS_CONSUMER_TIMER0_CC0_MASK)

/** Map TIMER reference to index of device. */
#if defined(TIMER4)
#define TIMER_DEVICE_ID(timer) ( \
    (timer) == TIMER0   ? 0      \
    : (timer) == TIMER1 ? 1      \
    : (timer) == TIMER2 ? 2      \
    : (timer) == TIMER3 ? 3      \
    : (timer) == TIMER4 ? 4      \
    : -1)
#else
#define TIMER_DEVICE_ID(timer) ( \
    (timer) == TIMER0   ? 0      \
    : (timer) == TIMER1 ? 1      \
    : (timer) == TIMER2 ? 2      \
    : (timer) == TIMER3 ? 3      \
    : -1)
#endif

#define TIMER_INPUT_CHANNEL_DTI     3UL
#define TIMER_INPUT_CHANNEL_DTIFS1  4UL
#define TIMER_INPUT_CHANNEL_DTIFS2  5UL

/**
 * TIMER PRS registers are moved into the PRS register space on series 2 devices.
 * The PRS Consumer registers for a timer consist of 6 registers.
 *
 * [0] - CC0 PRS Consumer
 * [1] - CC1 PRS Consumer
 * [2] - CC2 PRS Consumer
 * [3] - DTI PRS Consumer
 * [4] - DTIFS1 PRS Consumer
 * [5] - DTIFS2 PRS Consumer
 */
typedef struct {
  __IOM uint32_t CONSUMER_CH[6];         /**< TIMER PRS consumers. */
} PRS_TIMERn_Consumer_TypeDef;

typedef struct {
  PRS_TIMERn_Consumer_TypeDef TIMER_CONSUMER[TIMER_COUNT];
} PRS_TIMERn_TypeDef;

/**
 * @brief Configure a timer capture/compare channel to use a PRS channel as input.
 *
 * @param[in] timer
 *
 * @param[in] cc
 *   Timer input channel. Valid input is 0-5.
 *   0 - CC0
 *   1 - CC1
 *   2 - CC2
 *   3 - DTI
 *   4 - DTIFS1
 *   5 - DTIFS2
 *
 * @param[in] prsCh
 *   PRS channel number.
 *
 * @param[in] async
 *   true for asynchronous PRS channel, false for synchronous PRS channel.
 */
static void timerPrsConfig(TIMER_TypeDef * timer, unsigned int cc, unsigned int prsCh, bool async)
{
  int i = TIMER_DEVICE_ID(timer);
  PRS_TIMERn_TypeDef * base = (PRS_TIMERn_TypeDef *) &PRS->CONSUMER_TIMER0_CC0;
  EFM_ASSERT(i != -1);

  if (async) {
    base->TIMER_CONSUMER[i].CONSUMER_CH[cc] = prsCh << _PRS_CONSUMER_TIMER0_CC0_PRSSEL_SHIFT;
  } else {
    base->TIMER_CONSUMER[i].CONSUMER_CH[cc] = prsCh << _PRS_CONSUMER_TIMER0_CC0_SPRSSEL_SHIFT;
  }
}
#endif

/** @endcond */

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

/***************************************************************************//**
 * @brief
 *   Initialize TIMER.
 *
 * @details
 *   Notice that the counter top must be configured separately with, for instance
 *   TIMER_TopSet(). In addition, compare/capture and dead-time insertion
 *   initialization must be initialized separately if used, which should probably
 *   be done prior to using this function if configuring the TIMER to
 *   start when initialization is completed.
 *
 * @param[in] timer
 *   A pointer to the TIMER peripheral register block.
 *
 * @param[in] init
 *   A pointer to the TIMER initialization structure.
 ******************************************************************************/
void TIMER_Init(TIMER_TypeDef *timer, const TIMER_Init_TypeDef *init)
{
  EFM_ASSERT(TIMER_REF_VALID(timer));
  uint32_t ctrlRegVal = 0;

#if defined (_TIMER_CFG_PRESC_SHIFT)
  TIMER_SyncWait(timer);
  timer->EN_CLR = TIMER_EN_EN;
  timer->CFG = ((uint32_t)init->prescale << _TIMER_CFG_PRESC_SHIFT)
               | ((uint32_t)init->clkSel << _TIMER_CFG_CLKSEL_SHIFT)
               | ((uint32_t)init->mode   << _TIMER_CFG_MODE_SHIFT)
               | (init->debugRun         ?   TIMER_CFG_DEBUGRUN  : 0)
               | (init->dmaClrAct        ?   TIMER_CFG_DMACLRACT : 0)
               | (init->quadModeX4       ?   TIMER_CFG_QDM_X4    : 0)
               | (init->oneShot          ?   TIMER_CFG_OSMEN     : 0)
               | (init->sync             ?   TIMER_CFG_SYNC      : 0)
               | (init->ati              ?   TIMER_CFG_ATI       : 0);
  timer->EN_SET = TIMER_EN_EN;
#endif

  /* Stop the timer if specified to be disabled (doesn't hurt if already stopped). */
  if (!(init->enable)) {
    timer->CMD = TIMER_CMD_STOP;
  }

  /* Reset the counter. */
  timer->CNT = _TIMER_CNT_RESETVALUE;

#if defined(_SILICON_LABS_32B_SERIES_2)
  ctrlRegVal = ((uint32_t)init->fallAction   << _TIMER_CTRL_FALLA_SHIFT)
               | ((uint32_t)init->riseAction << _TIMER_CTRL_RISEA_SHIFT)
               | (init->count2x              ?   TIMER_CTRL_X2CNT     : 0);
#else
  ctrlRegVal = ((uint32_t)init->prescale     << _TIMER_CTRL_PRESC_SHIFT)
               | ((uint32_t)init->clkSel     << _TIMER_CTRL_CLKSEL_SHIFT)
               | ((uint32_t)init->fallAction << _TIMER_CTRL_FALLA_SHIFT)
               | ((uint32_t)init->riseAction << _TIMER_CTRL_RISEA_SHIFT)
               | ((uint32_t)init->mode       << _TIMER_CTRL_MODE_SHIFT)
               | (init->debugRun             ?   TIMER_CTRL_DEBUGRUN  : 0)
               | (init->dmaClrAct            ?   TIMER_CTRL_DMACLRACT : 0)
               | (init->quadModeX4           ?   TIMER_CTRL_QDM_X4    : 0)
               | (init->oneShot              ?   TIMER_CTRL_OSMEN     : 0)
               | (init->sync                 ?   TIMER_CTRL_SYNC      : 0);
#if defined(TIMER_CTRL_X2CNT) && defined(TIMER_CTRL_ATI)
  ctrlRegVal |= (init->count2x              ?   TIMER_CTRL_X2CNT     : 0)
                | (init->ati                ?   TIMER_CTRL_ATI       : 0);
#endif
#endif

  timer->CTRL = ctrlRegVal;

  /* Start the timer if specified to be enabled (doesn't hurt if already started). */
  if (init->enable) {
    timer->CMD = TIMER_CMD_START;
  }
}

/***************************************************************************//**
 * @brief
 *   Initialize the TIMER compare/capture channel.
 *
 * @details
 *   Notice that if operating the channel in compare mode, the CCV and CCVB register
 *   must be set separately, as required.
 *
 * @param[in] timer
 *   A pointer to the TIMER peripheral register block.
 *
 * @param[in] ch
 *   A compare/capture channel to initialize for.
 *
 * @param[in] init
 *   A pointer to the TIMER initialization structure.
 ******************************************************************************/
void TIMER_InitCC(TIMER_TypeDef *timer,
                  unsigned int ch,
                  const TIMER_InitCC_TypeDef *init)
{
  EFM_ASSERT(TIMER_REF_VALID(timer));
  EFM_ASSERT(TIMER_CH_VALID(ch));

#if defined (_TIMER_CC_CFG_MASK)
  TIMER_SyncWait(timer);
  timer->EN_CLR = TIMER_EN_EN;
  timer->CC[ch].CFG =
    ((uint32_t)init->mode        << _TIMER_CC_CFG_MODE_SHIFT)
    | (init->filter              ?   TIMER_CC_CFG_FILT_ENABLE : 0)
    | (init->coist               ?   TIMER_CC_CFG_COIST       : 0)
    | ((uint32_t)init->prsOutput << _TIMER_CC_CFG_PRSCONF_SHIFT);

  if (init->prsInput) {
    timer->CC[ch].CFG |= (uint32_t)init->prsInputType << _TIMER_CC_CFG_INSEL_SHIFT;
    bool async = (init->prsInputType != timerPrsInputSync);
    timerPrsConfig(timer, ch, init->prsSel, async);
  } else {
    timer->CC[ch].CFG |= TIMER_CC_CFG_INSEL_PIN;
  }
  timer->EN_SET = TIMER_EN_EN;

  timer->CC[ch].CTRL =
    ((uint32_t)init->eventCtrl << _TIMER_CC_CTRL_ICEVCTRL_SHIFT)
    | ((uint32_t)init->edge    << _TIMER_CC_CTRL_ICEDGE_SHIFT)
    | ((uint32_t)init->cufoa   << _TIMER_CC_CTRL_CUFOA_SHIFT)
    | ((uint32_t)init->cofoa   << _TIMER_CC_CTRL_COFOA_SHIFT)
    | ((uint32_t)init->cmoa    << _TIMER_CC_CTRL_CMOA_SHIFT)
    | (init->outInvert         ?   TIMER_CC_CTRL_OUTINV : 0);
#else
  timer->CC[ch].CTRL =
    ((uint32_t)init->eventCtrl   << _TIMER_CC_CTRL_ICEVCTRL_SHIFT)
    | ((uint32_t)init->edge      << _TIMER_CC_CTRL_ICEDGE_SHIFT)
    | ((uint32_t)init->prsSel    << _TIMER_CC_CTRL_PRSSEL_SHIFT)
    | ((uint32_t)init->cufoa     << _TIMER_CC_CTRL_CUFOA_SHIFT)
    | ((uint32_t)init->cofoa     << _TIMER_CC_CTRL_COFOA_SHIFT)
    | ((uint32_t)init->cmoa      << _TIMER_CC_CTRL_CMOA_SHIFT)
    | ((uint32_t)init->mode      << _TIMER_CC_CTRL_MODE_SHIFT)
    | (init->filter              ?   TIMER_CC_CTRL_FILT_ENABLE : 0)
    | (init->prsInput            ?   TIMER_CC_CTRL_INSEL_PRS   : 0)
    | (init->coist               ?   TIMER_CC_CTRL_COIST       : 0)
    | (init->outInvert           ?   TIMER_CC_CTRL_OUTINV      : 0)
#if defined(_TIMER_CC_CTRL_PRSCONF_MASK)
    | ((uint32_t)init->prsOutput << _TIMER_CC_CTRL_PRSCONF_SHIFT)
#endif
  ;
#endif
}

#if defined(_TIMER_DTCTRL_MASK)
/***************************************************************************//**
 * @brief
 *   Initialize the TIMER DTI unit.
 *
 * @param[in] timer
 *   A pointer to the TIMER peripheral register block.
 *
 * @param[in] init
 *   A pointer to the TIMER DTI initialization structure.
 ******************************************************************************/
void TIMER_InitDTI(TIMER_TypeDef *timer, const TIMER_InitDTI_TypeDef *init)
{
  EFM_ASSERT(TIMER0 == timer);

  /* Make sure the DTI unit is disabled while initializing. */
  TIMER_EnableDTI(timer, false);

#if defined (_TIMER_DTCFG_MASK)
  TIMER_SyncWait(timer);
  timer->EN_CLR = TIMER_EN_EN;
  timer->DTCFG = (init->autoRestart       ?   TIMER_DTCFG_DTDAS   : 0)
                 | (init->enablePrsSource ?   TIMER_DTCFG_DTPRSEN : 0);
  if (init->enablePrsSource) {
    timerPrsConfig(timer, TIMER_INPUT_CHANNEL_DTI, init->prsSel, true);
  }

  timer->DTTIMECFG =
    ((uint32_t)init->prescale   << _TIMER_DTTIMECFG_DTPRESC_SHIFT)
    | ((uint32_t)init->riseTime << _TIMER_DTTIMECFG_DTRISET_SHIFT)
    | ((uint32_t)init->fallTime << _TIMER_DTTIMECFG_DTFALLT_SHIFT);

  timer->DTFCFG =
    (init->enableFaultSourceCoreLockup ?   TIMER_DTFCFG_DTLOCKUPFEN : 0)
    | (init->enableFaultSourceDebugger ?   TIMER_DTFCFG_DTDBGFEN    : 0)
    | (init->enableFaultSourcePrsSel0  ?   TIMER_DTFCFG_DTPRS0FEN   : 0)
    | (init->enableFaultSourcePrsSel1  ?   TIMER_DTFCFG_DTPRS1FEN   : 0)
    | ((uint32_t)(init->faultAction)   << _TIMER_DTFCFG_DTFA_SHIFT);

  if (init->enableFaultSourcePrsSel0) {
    timerPrsConfig(timer, TIMER_INPUT_CHANNEL_DTIFS1, init->faultSourcePrsSel0, true);
  }
  if (init->enableFaultSourcePrsSel1) {
    timerPrsConfig(timer, TIMER_INPUT_CHANNEL_DTIFS2, init->faultSourcePrsSel1, true);
  }

  timer->EN_SET = TIMER_EN_EN;
#endif

#if defined(TIMER_DTCTRL_DTDAS)
  /* Set up the DTCTRL register.
     The enable bit will be set at the end of the function if specified. */
  timer->DTCTRL =
    (init->autoRestart              ?   TIMER_DTCTRL_DTDAS   : 0)
    | (init->activeLowOut           ?   TIMER_DTCTRL_DTIPOL  : 0)
    | (init->invertComplementaryOut ?   TIMER_DTCTRL_DTCINV  : 0)
    | (init->enablePrsSource        ?   TIMER_DTCTRL_DTPRSEN : 0)
    | ((uint32_t)(init->prsSel)     << _TIMER_DTCTRL_DTPRSSEL_SHIFT);
#endif

#if defined (TIMER_DTCFG_DTDAS)
  timer->DTCTRL = (init->activeLowOut             ? TIMER_DTCTRL_DTIPOL  : 0)
                  | (init->invertComplementaryOut ? TIMER_DTCTRL_DTCINV  : 0);
#endif

#if defined (_TIMER_DTTIME_DTPRESC_SHIFT)
  /* Set up the DTTIME register. */
  timer->DTTIME = ((uint32_t)init->prescale   << _TIMER_DTTIME_DTPRESC_SHIFT)
                  | ((uint32_t)init->riseTime << _TIMER_DTTIME_DTRISET_SHIFT)
                  | ((uint32_t)init->fallTime << _TIMER_DTTIME_DTFALLT_SHIFT);
#endif

#if defined (TIMER_DTFC_DTLOCKUPFEN)
  /* Set up the DTFC register. */
  timer->DTFC =
    (init->enableFaultSourceCoreLockup    ?   TIMER_DTFC_DTLOCKUPFEN : 0)
    | (init->enableFaultSourceDebugger    ?   TIMER_DTFC_DTDBGFEN    : 0)
    | (init->enableFaultSourcePrsSel0     ?   TIMER_DTFC_DTPRS0FEN   : 0)
    | (init->enableFaultSourcePrsSel1     ?   TIMER_DTFC_DTPRS1FEN   : 0)
    | ((uint32_t)init->faultAction        << _TIMER_DTFC_DTFA_SHIFT)
    | ((uint32_t)init->faultSourcePrsSel0 << _TIMER_DTFC_DTPRS0FSEL_SHIFT)
    | ((uint32_t)init->faultSourcePrsSel1 << _TIMER_DTFC_DTPRS1FSEL_SHIFT);
#endif

  /* Set up the DTOGEN register. */
  timer->DTOGEN = init->outputsEnableMask;

  /* Clear any previous DTI faults.  */
  TIMER_ClearDTIFault(timer, TIMER_GetDTIFault(timer));

  /* Enable/disable before returning. */
  TIMER_EnableDTI(timer, init->enable);
}
#endif

/***************************************************************************//**
 * @brief
 *   Reset the TIMER 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
 *   a centralized setup of this feature.
 *
 * @param[in] timer
 *   A pointer to the TIMER peripheral register block.
 ******************************************************************************/
void TIMER_Reset(TIMER_TypeDef *timer)
{
  int i;

  EFM_ASSERT(TIMER_REF_VALID(timer));

  /* Make sure disabled first, before resetting other registers. */
  timer->CMD = TIMER_CMD_STOP;

  timer->CTRL = _TIMER_CTRL_RESETVALUE;
  timer->IEN  = _TIMER_IEN_RESETVALUE;
#if defined (TIMER_HAS_SET_CLEAR)
  timer->IF_CLR = _TIMER_IF_MASK;
#else
  timer->IFC  = _TIMER_IFC_MASK;
#endif
  timer->TOPB = _TIMER_TOPB_RESETVALUE;
  /* Write TOP after TOPB to invalidate TOPB (clear TIMER_STATUS_TOPBV). */
  timer->TOP  = _TIMER_TOP_RESETVALUE;
  timer->CNT  = _TIMER_CNT_RESETVALUE;
  /* Do not reset the route register, setting should be done independently. */
  /* Note: The ROUTE register may be locked by the DTLOCK register. */

  for (i = 0; TIMER_CH_VALID(i); i++) {
    timer->CC[i].CTRL = _TIMER_CC_CTRL_RESETVALUE;
#if defined (_TIMER_CC_CCV_RESETVALUE) && defined (_TIMER_CC_CCVB_RESETVALUE)
    timer->CC[i].CCV  = _TIMER_CC_CCV_RESETVALUE;
    timer->CC[i].CCVB = _TIMER_CC_CCVB_RESETVALUE;
#endif
#if defined (_TIMER_CC_OC_RESETVALUE) && defined (_TIMER_CC_OCB_RESETVALUE) \
    && defined (_TIMER_CC_ICF_RESETVALUE) && defined (_TIMER_CC_ICOF_RESETVALUE)
    timer->CC[i].OC     = _TIMER_CC_OC_RESETVALUE;
    timer->CC[i].OCB    = _TIMER_CC_OCB_RESETVALUE;
#endif
  }

  /* Reset dead time insertion module, which has no effect on timers without DTI. */
#if defined(_TIMER_DTCFG_MASK)
  timer->DTLOCK   = TIMER_DTLOCK_DTILOCKKEY_UNLOCK;
  timer->DTCTRL   = _TIMER_DTCTRL_RESETVALUE;
  timer->DTOGEN   = _TIMER_DTOGEN_RESETVALUE;
  timer->DTFAULTC = _TIMER_DTFAULTC_MASK;
#elif defined(TIMER_DTLOCK_LOCKKEY_UNLOCK)
  /* Unlock DTI registers first if locked. */
  timer->DTLOCK   = TIMER_DTLOCK_LOCKKEY_UNLOCK;
  timer->DTCTRL   = _TIMER_DTCTRL_RESETVALUE;
  timer->DTTIME   = _TIMER_DTTIME_RESETVALUE;
  timer->DTFC     = _TIMER_DTFC_RESETVALUE;
  timer->DTOGEN   = _TIMER_DTOGEN_RESETVALUE;
  timer->DTFAULTC = _TIMER_DTFAULTC_MASK;
#endif

#if defined(_TIMER_CFG_MASK)
  TIMER_SyncWait(timer);
  /* CFG registers must be reset after the timer is disabled */
  timer->EN_CLR = TIMER_EN_EN;
  timer->CFG = _TIMER_CFG_RESETVALUE;
  for (i = 0; TIMER_CH_VALID(i); i++) {
    timer->CC[i].CFG = _TIMER_CC_CFG_RESETVALUE;
  }
  timer->DTCFG = _TIMER_DTCFG_RESETVALUE;
  timer->DTFCFG = _TIMER_DTFCFG_RESETVALUE;
  timer->DTTIMECFG = _TIMER_DTTIMECFG_RESETVALUE;
#endif
}

#if defined(TIMER_STATUS_SYNCBUSY)
/**
 * @brief Wait for pending synchronization to finish
 *
 * @param[in] timer
 */
void TIMER_SyncWait(TIMER_TypeDef * timer)
{
  while (((timer->EN & TIMER_EN_EN) != 0U)
         && ((timer->STATUS & TIMER_STATUS_SYNCBUSY) != 0U)) {
    /* Wait for synchronization to complete */
  }
}
#endif

/** @} (end addtogroup TIMER) */
/** @} (end addtogroup emlib) */
#endif /* defined(TIMER_COUNT) && (TIMER_COUNT > 0) */
