/***************************************************************************//** * @file * @brief Timer/counter (TIMER) 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_timer.h" #if defined(TIMER_COUNT) && (TIMER_COUNT > 0) #include "em_assert.h" /***************************************************************************//** * @addtogroup timer TIMER - Timer/Counter * @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; #if defined(_TIMER_EN_DISABLING_MASK) while (timer->EN & _TIMER_EN_DISABLING_MASK) { } #endif 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)); #if !defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) EFM_ASSERT(TIMER_CH_VALID(ch)); #endif #if defined (_TIMER_CC_CFG_MASK) TIMER_SyncWait(timer); timer->EN_CLR = TIMER_EN_EN; #if defined(_TIMER_EN_DISABLING_MASK) while (timer->EN & _TIMER_EN_DISABLING_MASK) { } #endif 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(TIMER_SupportsDTI(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; #if defined(_TIMER_EN_DISABLING_MASK) while (timer->EN & _TIMER_EN_DISABLING_MASK) { } #endif 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)); #if defined(TIMER_EN_EN) timer->EN_SET = TIMER_EN_EN; #endif /* 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. */ #if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) for (i = 0; TIMER_REF_CH_VALID(timer, i); i++) { #else for (i = 0; TIMER_CH_VALID(i); i++) { #endif 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; #if defined(_TIMER_EN_DISABLING_MASK) while (timer->EN & _TIMER_EN_DISABLING_MASK) { } #endif 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) */ #endif /* defined(TIMER_COUNT) && (TIMER_COUNT > 0) */