Initial commit of firmware

This commit is contained in:
2025-04-12 13:30:57 +01:00
commit 264a3462e0
374 changed files with 332649 additions and 0 deletions

View File

@@ -0,0 +1,709 @@
/***************************************************************************//**
* @file
* @brief Analog Comparator (ACMP) Peripheral API
*******************************************************************************
* # 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_acmp.h"
#if defined(ACMP_COUNT) && (ACMP_COUNT > 0)
#include <stdbool.h>
#include "em_bus.h"
#include "sl_assert.h"
#include "em_gpio.h"
/***************************************************************************//**
* @addtogroup acmp ACMP - Analog Comparator
* @brief Analog comparator (ACMP) Peripheral API
*
* @details
* The Analog Comparator is used to compare voltage of two analog inputs
* with a digital output indicating which input voltage is higher. Inputs can
* either be one of the selectable internal references or from external pins.
* Response time and current consumption can be configured by
* altering the current supply to the comparator.
*
* ACMP is available down to EM3 and is able to wake up the system when
* input signals pass a certain threshold. Use @ref ACMP_IntEnable() to enable
* an edge interrupt to use this functionality.
*
* This example shows how to use the em_acmp.h API for comparing an input
* pin to an internal 2.5 V reference voltage.
*
* @if DOXYDOC_P1_DEVICE
* @include em_acmp_compare_s0.c
* @endif
*
* @if DOXYDOC_P2_DEVICE
* @include em_acmp_compare_s1.c
* @endif
*
* @if DOXYDOC_S2_DEVICE
* @include em_acmp_compare_s2.c
* @endif
*
* @note
* ACMP can also be used to compare two separate input pins.
*
* @details
* ACMP also contains specialized hardware for capacitive sensing. This
* module contains the @ref ACMP_CapsenseInit() function to initialize
* ACMP for capacitive sensing and the @ref ACMP_CapsenseChannelSet() function
* to select the current capsense channel.
*
* For applications that require capacitive sensing it is recommended to use a
* library, such as cslib, which is provided by Silicon Labs.
*
* @{
******************************************************************************/
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/** Validation of ACMP register block pointer reference
* for assert statements. */
#if (ACMP_COUNT == 1)
#define ACMP_REF_VALID(ref) ((ref) == ACMP0)
#elif (ACMP_COUNT == 2)
#define ACMP_REF_VALID(ref) (((ref) == ACMP0) || ((ref) == ACMP1))
#elif (ACMP_COUNT == 3)
#define ACMP_REF_VALID(ref) (((ref) == ACMP0) || ((ref) == ACMP1) || ((ref) == ACMP2))
#elif (ACMP_COUNT == 4)
#define ACMP_REF_VALID(ref) (((ref) == ACMP0) \
|| ((ref) == ACMP1) \
|| ((ref) == ACMP2) \
|| ((ref) == ACMP3))
#else
#error Undefined number of analog comparators (ACMP).
#endif
/** The maximum value that can be inserted in the route location register
* for the specific device. */
#if defined(_ACMP_ROUTE_LOCATION_LOC3)
#define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTE_LOCATION_LOC3
#elif defined(_ACMP_ROUTE_LOCATION_LOC2)
#define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTE_LOCATION_LOC2
#elif defined(_ACMP_ROUTE_LOCATION_LOC1)
#define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTE_LOCATION_LOC1
#elif defined(_ACMP_ROUTELOC0_OUTLOC_LOC31)
#define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTELOC0_OUTLOC_LOC31
#elif defined(_ACMP_ROUTELOC0_OUTLOC_MASK)
#define _ACMP_ROUTE_LOCATION_MAX _ACMP_ROUTELOC0_OUTLOC_MASK
#endif
/** Map ACMP reference to index of device. */
#if (ACMP_COUNT == 1)
#define ACMP_DEVICE_ID(acmp) ( \
(acmp) == ACMP0 ? 0 \
: 0)
#elif (ACMP_COUNT == 2)
#define ACMP_DEVICE_ID(acmp) ( \
(acmp) == ACMP0 ? 0 \
: (acmp) == ACMP1 ? 1 \
: 0)
#endif
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Set up ACMP for use in capacitive sense applications.
*
* @details
* This function sets up ACMP for use in capacitive sense applications.
* To use the capacitive sense functionality in the ACMP, use
* the PRS output of the ACMP module to count the number of oscillations
* in the capacitive sense circuit (possibly using a TIMER).
*
* @note
* A basic example of capacitive sensing can be found in the STK BSP
* (capsense demo).
*
* @cond DOXYDOC_S2_DEVICE
* @note
* A call to ACMP_CapsenseInit will enable and disable the ACMP peripheral,
* which can cause side effects if it was previously set up.
* @endcond
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param[in] init
* A pointer to the initialization structure used to configure ACMP for capacitive
* sensing operation.
******************************************************************************/
void ACMP_CapsenseInit(ACMP_TypeDef *acmp, const ACMP_CapsenseInit_TypeDef *init)
{
EFM_ASSERT(ACMP_REF_VALID(acmp));
#if defined(_SILICON_LABS_32B_SERIES_2)
EFM_ASSERT(init->vrefDiv < 64);
EFM_ASSERT(init->biasProg
<= (_ACMP_CFG_BIAS_MASK >> _ACMP_CFG_BIAS_SHIFT));
ACMP_Disable(acmp);
acmp->CFG = (init->biasProg << _ACMP_CFG_BIAS_SHIFT)
| (init->hysteresisLevel << _ACMP_CFG_HYST_SHIFT);
acmp->CTRL = _ACMP_CTRL_RESETVALUE;
ACMP_Enable(acmp);
acmp->INPUTCTRL = (init->resistor << _ACMP_INPUTCTRL_CSRESSEL_SHIFT)
| (init->vrefDiv << _ACMP_INPUTCTRL_VREFDIV_SHIFT)
| (ACMP_INPUTCTRL_NEGSEL_CAPSENSE);
if (!init->enable) {
ACMP_Disable(acmp);
}
#elif defined(_SILICON_LABS_32B_SERIES_1)
EFM_ASSERT(init->vddLevelLow < 64);
EFM_ASSERT(init->vddLevelHigh < 64);
EFM_ASSERT(init->biasProg
<= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
/* Set the control register. No need to set interrupt modes. */
acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
| (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
| ACMP_CTRL_ACCURACY_HIGH;
acmp->HYSTERESIS0 = (init->vddLevelHigh << _ACMP_HYSTERESIS0_DIVVA_SHIFT)
| (init->hysteresisLevel_0 << _ACMP_HYSTERESIS0_HYST_SHIFT);
acmp->HYSTERESIS1 = (init->vddLevelLow << _ACMP_HYSTERESIS1_DIVVA_SHIFT)
| (init->hysteresisLevel_1 << _ACMP_HYSTERESIS1_HYST_SHIFT);
/* Select capacitive sensing mode by selecting a resistor and enabling it. */
acmp->INPUTSEL = (init->resistor << _ACMP_INPUTSEL_CSRESSEL_SHIFT)
| ACMP_INPUTSEL_CSRESEN
| ACMP_INPUTSEL_VASEL_VDD
| ACMP_INPUTSEL_NEGSEL_VADIV;
BUS_RegBitWrite(&acmp->CTRL, _ACMP_CTRL_EN_SHIFT, init->enable);
#elif defined(_SILICON_LABS_32B_SERIES_0)
EFM_ASSERT(init->vddLevel < 64);
EFM_ASSERT(init->biasProg
<= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
/* Set the control register. No need to set interrupt modes. */
acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
| (init->halfBias << _ACMP_CTRL_HALFBIAS_SHIFT)
| (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
| (init->warmTime << _ACMP_CTRL_WARMTIME_SHIFT)
| (init->hysteresisLevel << _ACMP_CTRL_HYSTSEL_SHIFT);
/* Select capacitive sensing mode by selecting a resistor and enabling it. */
acmp->INPUTSEL = (init->resistor << _ACMP_INPUTSEL_CSRESSEL_SHIFT)
| ACMP_INPUTSEL_CSRESEN
| (init->lowPowerReferenceEnabled << _ACMP_INPUTSEL_LPREF_SHIFT)
| (init->vddLevel << _ACMP_INPUTSEL_VDDLEVEL_SHIFT)
| ACMP_INPUTSEL_NEGSEL_CAPSENSE;
BUS_RegBitWrite(&acmp->CTRL, _ACMP_CTRL_EN_SHIFT, init->enable);
#endif
}
/***************************************************************************//**
* @brief
* Set the ACMP channel used for capacitive sensing.
*
* @note
* A basic example of capacitive sensing can be found in the STK BSP
* (capsense demo).
*
* @cond DOXYDOC_S2_DEVICE
* @note
* Can only be called when the peripheral is enabled.
* @endcond
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param[in] channel
* The ACMP channel to use for capacitive sensing (Possel).
******************************************************************************/
void ACMP_CapsenseChannelSet(ACMP_TypeDef *acmp, ACMP_Channel_TypeDef channel)
{
/* Make sure the module exists on the selected chip. */
EFM_ASSERT(ACMP_REF_VALID(acmp));
#if defined(_ACMP_INPUTSEL_POSSEL_CH7)
/* Make sure that only external channels are used. */
EFM_ASSERT(channel <= _ACMP_INPUTSEL_POSSEL_CH7);
#elif defined(_ACMP_INPUTCTRL_POSSEL_PD15)
EFM_ASSERT(channel != _ACMP_INPUTCTRL_NEGSEL_CAPSENSE);
EFM_ASSERT(_ACMP_INPUTCTRL_POSSEL_PA0 <= channel);
EFM_ASSERT(channel <= _ACMP_INPUTCTRL_POSSEL_PD15);
#endif
#if defined(_ACMP_INPUTCTRL_MASK)
/* Make sure that the ACMP is enabled before changing INPUTCTRL. */
EFM_ASSERT(acmp->EN & ACMP_EN_EN);
while (acmp->SYNCBUSY != 0U) {
/* Wait for synchronization to finish */
}
/* Set channel as positive channel in ACMP */
BUS_RegMaskedWrite(&acmp->INPUTCTRL, _ACMP_INPUTCTRL_POSSEL_MASK,
channel << _ACMP_INPUTCTRL_POSSEL_SHIFT);
#else
/* Set channel as a positive channel in ACMP. */
BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_POSSEL_MASK,
channel << _ACMP_INPUTSEL_POSSEL_SHIFT);
#endif
}
/***************************************************************************//**
* @brief
* Disable ACMP.
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
******************************************************************************/
void ACMP_Disable(ACMP_TypeDef *acmp)
{
/* Make sure the module exists on the selected chip. */
EFM_ASSERT(ACMP_REF_VALID(acmp));
#if defined(_ACMP_EN_MASK)
while ((acmp->EN != 0U) && (acmp->SYNCBUSY != 0U)) {
/* Wait for synchronization to finish */
}
acmp->EN_CLR = ACMP_EN_EN;
#if defined(_ACMP_EN_DISABLING_MASK)
while (acmp->EN & _ACMP_EN_DISABLING_MASK) {
// Wait for disabling to finish
}
#endif
#else
acmp->CTRL &= ~ACMP_CTRL_EN;
#endif
}
/***************************************************************************//**
* @brief
* Enable ACMP.
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
******************************************************************************/
void ACMP_Enable(ACMP_TypeDef *acmp)
{
/* Make sure the module exists on the selected chip. */
EFM_ASSERT(ACMP_REF_VALID(acmp));
#if defined(_ACMP_EN_MASK)
acmp->EN_SET = ACMP_EN_EN;
#else
acmp->CTRL |= ACMP_CTRL_EN;
#endif
}
#if defined(_ACMP_EXTIFCTRL_MASK)
/***************************************************************************//**
* @brief
* Select and enable external input.
*
* @details
* This is used when an external module needs to take control of the ACMP
* POSSEL field to configure the APORT input for the ACMP. Modules,
* such as LESENSE, use this to change the ACMP input during a scan sequence.
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param[in] aport
* This parameter decides which APORT(s) the ACMP will use when it's
* controlled by an external module.
******************************************************************************/
void ACMP_ExternalInputSelect(ACMP_TypeDef *acmp, ACMP_ExternalInput_Typedef aport)
{
acmp->EXTIFCTRL = (aport << _ACMP_EXTIFCTRL_APORTSEL_SHIFT)
| ACMP_EXTIFCTRL_EN;
while (!(acmp->STATUS & ACMP_STATUS_EXTIFACT)) {
}
}
#endif
/***************************************************************************//**
* @brief
* Reset ACMP to the same state that it was in after a hardware reset.
*
* @note
* The GPIO ACMP ROUTE register is NOT reset by this function to allow for
* centralized setup of this feature.
*
* @note
* The peripheral may be enabled and disabled during reset.
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
******************************************************************************/
void ACMP_Reset(ACMP_TypeDef *acmp)
{
/* Make sure the module exists on the selected chip */
EFM_ASSERT(ACMP_REF_VALID(acmp));
#if defined(_SILICON_LABS_32B_SERIES_2)
#if defined(ACMP_SWRST_SWRST)
acmp->SWRST_SET = ACMP_SWRST_SWRST;
while (acmp->SWRST & _ACMP_SWRST_RESETTING_MASK) {
}
#else
acmp->IEN = _ACMP_IEN_RESETVALUE;
ACMP_Enable(acmp);
acmp->INPUTCTRL = _ACMP_INPUTCTRL_RESETVALUE;
ACMP_Disable(acmp);
acmp->CFG = PM5507_ACMP_CFG_RESETVALUE;
acmp->CTRL = _ACMP_CTRL_RESETVALUE;
acmp->IF_CLR = _ACMP_IF_MASK;
#endif
#else // Series 0 and Series 1 devices
acmp->IEN = _ACMP_IEN_RESETVALUE;
acmp->CTRL = _ACMP_CTRL_RESETVALUE;
acmp->INPUTSEL = _ACMP_INPUTSEL_RESETVALUE;
#if defined(_ACMP_HYSTERESIS0_HYST_MASK)
acmp->HYSTERESIS0 = _ACMP_HYSTERESIS0_RESETVALUE;
acmp->HYSTERESIS1 = _ACMP_HYSTERESIS1_RESETVALUE;
#endif
acmp->IFC = _ACMP_IF_MASK;
#endif
}
#if defined(_GPIO_ACMP_ROUTEEN_MASK)
/***************************************************************************//**
* @brief
* Sets up GPIO output from the ACMP.
*
* @note
* GPIO must be enabled in the CMU before this function call, i.e.
* @verbatim CMU_ClockEnable(cmuClock_GPIO, true); @endverbatim
*
* @param[in] acmp
* Pointer to the ACMP peripheral register block.
*
* @param port
* The GPIO port to use.
*
* @param pin
* The GPIO pin to use.
*
* @param enable
* Enable or disable pin output.
*
* @param invert
* Invert output.
******************************************************************************/
void ACMP_GPIOSetup(ACMP_TypeDef *acmp, GPIO_Port_TypeDef port,
unsigned int pin, bool enable, bool invert)
{
int acmpIndex = ACMP_DEVICE_ID(acmp);
/* Make sure the module exists on the selected chip */
EFM_ASSERT(ACMP_REF_VALID(acmp));
/* Make sure that the port/pin combination is valid. */
EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin));
/* Set GPIO inversion */
acmp->CTRL = (acmp->CTRL & _ACMP_CTRL_NOTRDYVAL_MASK)
| (invert << _ACMP_CTRL_GPIOINV_SHIFT);
GPIO->ACMPROUTE[acmpIndex].ACMPOUTROUTE = (port << _GPIO_ACMP_ACMPOUTROUTE_PORT_SHIFT)
| (pin << _GPIO_ACMP_ACMPOUTROUTE_PIN_SHIFT);
GPIO->ACMPROUTE[acmpIndex].ROUTEEN = enable ? GPIO_ACMP_ROUTEEN_ACMPOUTPEN : 0;
}
#else
/***************************************************************************//**
* @brief
* Set up GPIO output from ACMP.
*
* @note
* GPIO must be enabled in the CMU before this function call, i.e.,
* @verbatim CMU_ClockEnable(cmuClock_GPIO, true); @endverbatim
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param location
* The pin location to use. See the data sheet for location to pin mappings.
*
* @param enable
* Enable or disable pin output.
*
* @param invert
* Invert output.
******************************************************************************/
void ACMP_GPIOSetup(ACMP_TypeDef *acmp, uint32_t location, bool enable, bool invert)
{
/* Make sure the module exists on the selected chip */
EFM_ASSERT(ACMP_REF_VALID(acmp));
/* Sanity checking of location */
EFM_ASSERT(location <= _ACMP_ROUTE_LOCATION_MAX);
/* Set GPIO inversion */
BUS_RegMaskedWrite(&acmp->CTRL, _ACMP_CTRL_GPIOINV_MASK,
invert << _ACMP_CTRL_GPIOINV_SHIFT);
#if defined(_ACMP_ROUTE_MASK)
acmp->ROUTE = (location << _ACMP_ROUTE_LOCATION_SHIFT)
| (enable << _ACMP_ROUTE_ACMPPEN_SHIFT);
#endif
#if defined(_ACMP_ROUTELOC0_MASK)
acmp->ROUTELOC0 = location << _ACMP_ROUTELOC0_OUTLOC_SHIFT;
acmp->ROUTEPEN = enable ? ACMP_ROUTEPEN_OUTPEN : 0;
#endif
}
#endif /* defined(_GPIO_ACMP_ROUTEEN_MASK) */
/***************************************************************************//**
* @brief
* Set which channels should be used in ACMP comparisons.
*
* @cond DOXYDOC_S2_DEVICE
* @note
* Can only be called when the peripheral is enabled.
*
* @note
* If GPIO is used for both posSel and negSel, they cannot both use even
* or odd pins.
* @endcond
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param negSel
* A channel to use on the negative input to the ACMP.
*
* @param posSel
* A channel to use on the positive input to the ACMP.
******************************************************************************/
void ACMP_ChannelSet(ACMP_TypeDef *acmp, ACMP_Channel_TypeDef negSel,
ACMP_Channel_TypeDef posSel)
{
/* Make sure the module exists on the selected chip. */
EFM_ASSERT(ACMP_REF_VALID(acmp));
/* Make sure that posSel and negSel channel selectors are valid. */
#if defined(_ACMP_INPUTSEL_NEGSEL_DAC0CH1)
EFM_ASSERT(negSel <= _ACMP_INPUTSEL_NEGSEL_DAC0CH1);
#elif defined(_ACMP_INPUTSEL_NEGSEL_CAPSENSE)
EFM_ASSERT(negSel <= _ACMP_INPUTSEL_NEGSEL_CAPSENSE);
#endif
#if defined(_ACMP_INPUTSEL_POSSEL_CH7)
EFM_ASSERT(posSel <= _ACMP_INPUTSEL_POSSEL_CH7);
#endif
/* Make sure that posSel and negSel channel selectors are valid. */
#if defined(_ACMP_INPUTCTRL_POSSEL_PD15)
EFM_ASSERT(negSel <= _ACMP_INPUTCTRL_POSSEL_PD15);
EFM_ASSERT(posSel <= _ACMP_INPUTCTRL_POSSEL_PD15);
EFM_ASSERT(posSel != _ACMP_INPUTCTRL_NEGSEL_CAPSENSE);
/* Make sure that posSel and negSel channel selectors don't both
* use odd or even pins. */
#if (_SILICON_LABS_32B_SERIES_2_CONFIG > 2)
EFM_ASSERT(!((((posSel >= _ACMP_INPUTCTRL_POSSEL_EXTPA)
&& (posSel <= _ACMP_INPUTCTRL_POSSEL_EXTPD))
|| (posSel >= _ACMP_INPUTCTRL_POSSEL_PA0))
&& (negSel >= _ACMP_INPUTCTRL_NEGSEL_PA0)
&& (posSel % 2 == negSel % 2)));
#else
EFM_ASSERT(!((posSel >= _ACMP_INPUTCTRL_POSSEL_PA0)
&& (negSel >= _ACMP_INPUTCTRL_NEGSEL_PA0)
&& (posSel % 2 == negSel % 2)));
#endif
#endif
#if defined(_ACMP_INPUTCTRL_MASK)
/* Make sure that the ACMP is enabled before changing INPUTCTRL. */
EFM_ASSERT(acmp->EN & ACMP_EN_EN);
while (acmp->SYNCBUSY != 0U) {
/* Wait for synchronization to finish */
}
acmp->INPUTCTRL = (acmp->INPUTCTRL & ~(_ACMP_INPUTCTRL_POSSEL_MASK
| _ACMP_INPUTCTRL_NEGSEL_MASK))
| (negSel << _ACMP_INPUTCTRL_NEGSEL_SHIFT)
| (posSel << _ACMP_INPUTCTRL_POSSEL_SHIFT);
#else
acmp->INPUTSEL = (acmp->INPUTSEL & ~(_ACMP_INPUTSEL_POSSEL_MASK
| _ACMP_INPUTSEL_NEGSEL_MASK))
| (negSel << _ACMP_INPUTSEL_NEGSEL_SHIFT)
| (posSel << _ACMP_INPUTSEL_POSSEL_SHIFT);
#endif
}
/***************************************************************************//**
* @brief
* Initialize ACMP.
*
* @cond DOXYDOC_S2_DEVICE
* @note
* A call to ACMP_Init can cause side effects since it can enable/disable
* the peripheral.
* @endcond
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param[in] init
* A pointer to the initialization structure used to configure ACMP.
******************************************************************************/
void ACMP_Init(ACMP_TypeDef *acmp, const ACMP_Init_TypeDef *init)
{
/* Make sure the module exists on the selected chip. */
EFM_ASSERT(ACMP_REF_VALID(acmp));
#if defined(_SILICON_LABS_32B_SERIES_2)
EFM_ASSERT(init->biasProg
<= (_ACMP_CFG_BIAS_MASK >> _ACMP_CFG_BIAS_SHIFT));
// PM-5507: enforce that biasProg is a functional value
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1)
EFM_ASSERT(init->biasProg >= 4);
#elif defined(_SILICON_LABS_32B_SERIES_2_CONFIG_3)
// Allow customer to use BIASPROG in [2; 3]
EFM_ASSERT(init->biasProg >= 2);
#else
// Allow customer to use BIASPROG in [0; 3]
// but the implementation of the wait operation would be their responsibility
#endif
/* Make sure the ACMP is disabled since ACMP power source might be changed.*/
ACMP_Disable(acmp);
acmp->CFG = (init->biasProg << _ACMP_CFG_BIAS_SHIFT)
| (init->inputRange << _ACMP_CFG_INPUTRANGE_SHIFT)
| (init->accuracy << _ACMP_CFG_ACCURACY_SHIFT)
| (init->hysteresisLevel << _ACMP_CFG_HYST_SHIFT);
acmp->CTRL = init->inactiveValue << _ACMP_CTRL_NOTRDYVAL_SHIFT;
ACMP_Enable(acmp);
BUS_RegMaskedWrite(&acmp->INPUTCTRL, _ACMP_INPUTCTRL_VREFDIV_MASK,
init->vrefDiv << _ACMP_INPUTCTRL_VREFDIV_SHIFT);
#elif defined(_SILICON_LABS_32B_SERIES_1)
EFM_ASSERT(init->biasProg
<= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
/* Make sure the ACMP is disabled since ACMP power source might be changed.*/
ACMP_Disable(acmp);
acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
| (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
| (init->interruptOnFallingEdge << _ACMP_CTRL_IFALL_SHIFT)
| (init->interruptOnRisingEdge << _ACMP_CTRL_IRISE_SHIFT)
| (init->inputRange << _ACMP_CTRL_INPUTRANGE_SHIFT)
| (init->accuracy << _ACMP_CTRL_ACCURACY_SHIFT)
| (init->powerSource << _ACMP_CTRL_PWRSEL_SHIFT)
| (init->inactiveValue << _ACMP_CTRL_INACTVAL_SHIFT);
acmp->INPUTSEL = init->vlpInput << _ACMP_INPUTSEL_VLPSEL_SHIFT;
acmp->HYSTERESIS0 = init->hysteresisLevel_0;
acmp->HYSTERESIS1 = init->hysteresisLevel_1;
#elif defined(_SILICON_LABS_32B_SERIES_0)
EFM_ASSERT(init->biasProg
<= (_ACMP_CTRL_BIASPROG_MASK >> _ACMP_CTRL_BIASPROG_SHIFT));
/* Make sure the ACMP is disabled since ACMP power source might be changed.*/
ACMP_Disable(acmp);
acmp->CTRL = (init->fullBias << _ACMP_CTRL_FULLBIAS_SHIFT)
| (init->halfBias << _ACMP_CTRL_HALFBIAS_SHIFT)
| (init->biasProg << _ACMP_CTRL_BIASPROG_SHIFT)
| (init->interruptOnFallingEdge << _ACMP_CTRL_IFALL_SHIFT)
| (init->interruptOnRisingEdge << _ACMP_CTRL_IRISE_SHIFT)
| (init->warmTime << _ACMP_CTRL_WARMTIME_SHIFT)
| (init->hysteresisLevel << _ACMP_CTRL_HYSTSEL_SHIFT)
| (init->inactiveValue << _ACMP_CTRL_INACTVAL_SHIFT);
acmp->INPUTSEL = (init->lowPowerReferenceEnabled << _ACMP_INPUTSEL_LPREF_SHIFT)
| (init->vddLevel << _ACMP_INPUTSEL_VDDLEVEL_SHIFT);
#endif
if (init->enable) {
ACMP_Enable(acmp);
} else {
ACMP_Disable(acmp);
}
}
#if defined(_ACMP_INPUTSEL_VASEL_MASK)
/***************************************************************************//**
* @brief
* Set up the VA source.
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param[in] vaconfig
* A pointer to the structure used to configure the VA source. This structure
* contains the input source and the 2 divider values.
******************************************************************************/
void ACMP_VASetup(ACMP_TypeDef *acmp, const ACMP_VAConfig_TypeDef *vaconfig)
{
EFM_ASSERT(vaconfig->div0 < 64);
EFM_ASSERT(vaconfig->div1 < 64);
BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_VASEL_MASK,
vaconfig->input << _ACMP_INPUTSEL_VASEL_SHIFT);
BUS_RegMaskedWrite(&acmp->HYSTERESIS0, _ACMP_HYSTERESIS0_DIVVA_MASK,
vaconfig->div0 << _ACMP_HYSTERESIS0_DIVVA_SHIFT);
BUS_RegMaskedWrite(&acmp->HYSTERESIS1, _ACMP_HYSTERESIS1_DIVVA_MASK,
vaconfig->div1 << _ACMP_HYSTERESIS1_DIVVA_SHIFT);
}
#endif
#if defined(_ACMP_INPUTSEL_VBSEL_MASK)
/***************************************************************************//**
* @brief
* Set up the VB Source.
*
* @param[in] acmp
* A pointer to the ACMP peripheral register block.
*
* @param[in] vbconfig
* A pointer to the structure used to configure the VB source. This structure
* contains the input source and the 2 divider values.
******************************************************************************/
void ACMP_VBSetup(ACMP_TypeDef *acmp, const ACMP_VBConfig_TypeDef *vbconfig)
{
EFM_ASSERT(vbconfig->div0 < 64);
EFM_ASSERT(vbconfig->div1 < 64);
BUS_RegMaskedWrite(&acmp->INPUTSEL, _ACMP_INPUTSEL_VBSEL_MASK,
vbconfig->input << _ACMP_INPUTSEL_VBSEL_SHIFT);
BUS_RegMaskedWrite(&acmp->HYSTERESIS0, _ACMP_HYSTERESIS0_DIVVB_MASK,
vbconfig->div0 << _ACMP_HYSTERESIS0_DIVVB_SHIFT);
BUS_RegMaskedWrite(&acmp->HYSTERESIS1, _ACMP_HYSTERESIS1_DIVVB_MASK,
vbconfig->div1 << _ACMP_HYSTERESIS1_DIVVB_SHIFT);
}
#endif
/** @} (end addtogroup acmp) */
#endif /* defined(ACMP_COUNT) && (ACMP_COUNT > 0) */

View File

@@ -0,0 +1,433 @@
/***************************************************************************//**
* @file
* @brief Backup Real Time Counter (BURTC) Peripheral API
*******************************************************************************
* # 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_burtc.h"
#if defined(BURTC_PRESENT)
/***************************************************************************//**
* @addtogroup burtc BURTC - Backup RTC
* @brief Backup Real Time Counter (BURTC) Peripheral API
* @details
* This module contains functions to control the BURTC peripheral of Silicon
* Labs 32-bit MCUs. The Backup Real Time Counter allows timekeeping in all
* energy modes. The Backup RTC is also available when the system is in backup
* mode.
* @{
******************************************************************************/
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/*******************************************************************************
************************** LOCAL FUNCTIONS ********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/***************************************************************************//**
* @brief Convert dividend to a prescaler logarithmic value. Only works for even
* numbers equal to 2^n.
* @param[in] div Unscaled dividend,
* @return Base 2 logarithm of input, as used by fixed prescalers.
******************************************************************************/
__STATIC_INLINE uint32_t divToLog2(uint32_t div)
{
uint32_t log2;
/* Prescaler accepts an argument of 128 or less, valid values being 2^n. */
EFM_ASSERT((div > 0UL) && (div <= 32768UL));
/* Count leading zeroes and "reverse" result, Cortex-M3 intrinsic. */
log2 = (31UL - __CLZ(div));
return log2;
}
/***************************************************************************//**
* @brief
* Wait for an ongoing sync of register(s) to low frequency domain to complete.
*
* @param[in] mask
* A bitmask corresponding to SYNCBUSY register defined bits, indicating
* registers that must complete any ongoing synchronization.
******************************************************************************/
__STATIC_INLINE void regSync(uint32_t mask)
{
#if defined(_BURTC_FREEZE_MASK)
/* Avoid deadlock if modifying the same register twice when freeze mode is
activated or when a clock is not selected for the BURTC. If a clock is
not selected, then the sync is done once the clock source is set. */
if ((BURTC->FREEZE & BURTC_FREEZE_REGFREEZE)
|| ((BURTC->CTRL & _BURTC_CTRL_CLKSEL_MASK) == BURTC_CTRL_CLKSEL_NONE)
|| ((BURTC->CTRL & _BURTC_CTRL_RSTEN_MASK) == BURTC_CTRL_RSTEN)) {
return;
}
#endif
/* Wait for any pending previous write operation to complete */
/* in low frequency domain. This is only required for the Gecko Family. */
while ((BURTC->SYNCBUSY & mask) != 0U) {
}
}
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief Initialize BURTC.
*
* @details
* Configures the BURTC peripheral.
*
* @note
* Before initialization, BURTC module must first be enabled by clearing the
* reset bit in the RMU, i.e.,
* @verbatim
* RMU_ResetControl(rmuResetBU, rmuResetModeClear);
* @endverbatim
* Compare channel 0 must be configured outside this function, before
* initialization if enable is set to true. The counter will always be reset.
*
* @param[in] burtcInit
* A pointer to the BURTC initialization structure.
******************************************************************************/
void BURTC_Init(const BURTC_Init_TypeDef *burtcInit)
{
#if defined(_SILICON_LABS_32B_SERIES_0)
uint32_t ctrl;
uint32_t presc;
/* Check initializer structure integrity. */
EFM_ASSERT(burtcInit != (BURTC_Init_TypeDef *) 0);
/* Clock divider must be between 1 and 128, really on the form 2^n. */
EFM_ASSERT((burtcInit->clkDiv >= 1) && (burtcInit->clkDiv <= 128));
/* Ignored compare bits during low power operation must be less than 7. */
/* Note! Giant Gecko revision C errata, do NOT use LPCOMP=7. */
EFM_ASSERT(burtcInit->lowPowerComp <= 6);
/* You cannot enable the BURTC if mode is set to disabled. */
EFM_ASSERT((burtcInit->enable == false)
|| ((burtcInit->enable == true)
&& (burtcInit->mode != burtcModeDisable)));
/* Low power mode is only available with LFRCO or LFXO as clock source. */
EFM_ASSERT((burtcInit->clkSel != burtcClkSelULFRCO)
|| ((burtcInit->clkSel == burtcClkSelULFRCO)
&& (burtcInit->lowPowerMode == burtcLPDisable)));
/* Calculate a prescaler value from the clock divider input. */
/* Note! If clock select (clkSel) is ULFRCO, a clock divisor (clkDiv) of
value 1 will select a 2 kHz ULFRCO clock, while any other value will
select a 1 kHz ULFRCO clock source. */
presc = divToLog2(burtcInit->clkDiv);
/* Make sure all registers are updated simultaneously. */
if (burtcInit->enable) {
BURTC_FreezeEnable(true);
}
/* Modification of LPMODE register requires sync with potential ongoing
* register updates in LF domain. */
regSync(BURTC_SYNCBUSY_LPMODE);
/* Configure low power mode. */
BURTC->LPMODE = (uint32_t) (burtcInit->lowPowerMode);
/* New configuration. */
ctrl = (BURTC_CTRL_RSTEN
| (burtcInit->mode)
| (burtcInit->debugRun << _BURTC_CTRL_DEBUGRUN_SHIFT)
| (burtcInit->compare0Top << _BURTC_CTRL_COMP0TOP_SHIFT)
| (burtcInit->lowPowerComp << _BURTC_CTRL_LPCOMP_SHIFT)
| (presc << _BURTC_CTRL_PRESC_SHIFT)
| (burtcInit->clkSel)
| (burtcInit->timeStamp << _BURTC_CTRL_BUMODETSEN_SHIFT));
/* Clear interrupts. */
BURTC_IntClear(0xFFFFFFFF);
/* Set the new configuration. */
BURTC->CTRL = ctrl;
/* Enable BURTC and counter. */
if (burtcInit->enable) {
/* To enable BURTC counter, disable reset. */
BURTC_Enable(true);
/* Clear freeze. */
BURTC_FreezeEnable(false);
}
#elif defined(_SILICON_LABS_32B_SERIES_2)
uint32_t presc;
presc = divToLog2(burtcInit->clkDiv);
if (BURTC->EN != 0U) {
BURTC_SyncWait();
}
BURTC->EN_CLR = BURTC_EN_EN;
#if defined(_BURTC_SYNCBUSY_EN_MASK)
regSync(BURTC_SYNCBUSY_EN);
#elif defined(_BURTC_EN_DISABLING_MASK)
while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
/* Wait for disabling to finish */
}
#endif
BURTC->CFG = (presc << _BURTC_CFG_CNTPRESC_SHIFT)
| ((burtcInit->compare0Top ? 1UL : 0UL) << _BURTC_CFG_COMPTOP_SHIFT)
| ((burtcInit->debugRun ? 1UL : 0UL) << _BURTC_CFG_DEBUGRUN_SHIFT);
BURTC->EM4WUEN = ((burtcInit->em4comp ? 1UL : 0UL) << _BURTC_EM4WUEN_COMPEM4WUEN_SHIFT)
| ((burtcInit->em4overflow ? 1UL : 0UL) << _BURTC_EM4WUEN_OFEM4WUEN_SHIFT);
BURTC->EN_SET = BURTC_EN_EN;
if (burtcInit->start) {
BURTC_Start();
}
#endif
}
#if defined(_SILICON_LABS_32B_SERIES_2)
/***************************************************************************//**
* @brief
* Enable or Disable BURTC peripheral.
*
* @param[in] enable
* true to enable, false to disable.
******************************************************************************/
void BURTC_Enable(bool enable)
{
#if defined(_BURTC_SYNCBUSY_EN_MASK)
regSync(BURTC_SYNCBUSY_EN);
#endif
if ((BURTC->EN == 0U) && !enable) {
/* Trying to disable BURTC when it's already disabled */
return;
}
if (BURTC->EN != 0U) {
/* Modifying the enable bit while synchronization is active will BusFault */
BURTC_SyncWait();
}
if (enable) {
BURTC->EN_SET = BURTC_EN_EN;
} else {
BURTC_Stop();
BURTC_SyncWait(); /* Wait for the stop to synchronize */
BURTC->EN_CLR = BURTC_EN_EN;
#if defined(_BURTC_SYNCBUSY_EN_MASK)
regSync(BURTC_SYNCBUSY_EN);
#elif defined(_BURTC_EN_DISABLING_MASK)
while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
/* Wait for disabling to finish */
}
#endif
}
}
#elif defined(_SILICON_LABS_32B_SERIES_0)
/***************************************************************************//**
* @brief
* Enable or Disable BURTC peripheral reset and start counter
* @param[in] enable
* If true; asserts reset to BURTC, halts counter, if false; deassert reset
******************************************************************************/
void BURTC_Enable(bool enable)
{
/* Note! If mode is disabled, BURTC counter will not start */
EFM_ASSERT(((enable == true)
&& ((BURTC->CTRL & _BURTC_CTRL_MODE_MASK)
!= BURTC_CTRL_MODE_DISABLE))
|| (enable == false));
BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, (uint32_t) !enable);
}
#endif
/***************************************************************************//**
* @brief Set BURTC compare channel.
*
* @param[in] comp Compare the channel index, must be 0 for current devices.
*
* @param[in] value New compare value.
******************************************************************************/
void BURTC_CompareSet(unsigned int comp, uint32_t value)
{
(void) comp; /* Unused parameter when EFM_ASSERT is undefined. */
EFM_ASSERT(comp == 0U);
#if defined(_BURTC_COMP0_MASK)
/* Modification of COMP0 register requires sync with potential ongoing
* register updates in LF domain. */
regSync(BURTC_SYNCBUSY_COMP0);
/* Configure compare channel 0/. */
BURTC->COMP0 = value;
#else
/* Wait for last potential write to complete. */
regSync(BURTC_SYNCBUSY_COMP);
/* Configure compare channel 0 */
BURTC->COMP = value;
regSync(BURTC_SYNCBUSY_COMP);
#endif
}
/***************************************************************************//**
* @brief Get the BURTC compare value.
*
* @param[in] comp Compare the channel index value, must be 0 for Giant/Leopard Gecko.
*
* @return The currently configured value for this compare channel.
******************************************************************************/
uint32_t BURTC_CompareGet(unsigned int comp)
{
(void) comp; /* Unused parameter when EFM_ASSERT is undefined. */
EFM_ASSERT(comp == 0U);
#if defined(_BURTC_COMP0_MASK)
return BURTC->COMP0;
#else
return BURTC->COMP;
#endif
}
/***************************************************************************//**
* @brief Reset counter
******************************************************************************/
void BURTC_CounterReset(void)
{
#if defined(_BURTC_CTRL_MASK)
/* Set and clear reset bit */
BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 1U);
BUS_RegBitWrite(&BURTC->CTRL, _BURTC_CTRL_RSTEN_SHIFT, 0U);
#else
BURTC_Stop();
BURTC->CNT = 0U;
BURTC_Start();
#endif
}
/***************************************************************************//**
* @brief
* Restore BURTC to reset state.
* @note
* Before accessing the BURTC, BURSTEN in RMU->CTRL must be cleared.
* LOCK will not be reset to default value, as this will disable access
* to core BURTC registers.
******************************************************************************/
void BURTC_Reset(void)
{
#if defined(_SILICON_LABS_32B_SERIES_0)
bool buResetState;
/* Read reset state, set reset, and restore state. */
buResetState = BUS_RegBitRead(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT);
BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, 1);
BUS_RegBitWrite(&RMU->CTRL, _RMU_CTRL_BURSTEN_SHIFT, buResetState);
#elif defined(_SILICON_LABS_32B_SERIES_2)
if (BURTC->EN != 0U) {
BURTC_SyncWait();
}
BURTC->EN_SET = BURTC_EN_EN;
BURTC_Stop();
BURTC->CNT = 0x0;
BURTC->PRECNT = 0x0;
BURTC->COMP = 0x0;
BURTC->EM4WUEN = _BURTC_EM4WUEN_RESETVALUE;
BURTC->IEN = _BURTC_IEN_RESETVALUE;
BURTC->IF_CLR = _BURTC_IF_MASK;
/* Wait for all values to synchronize. BusFaults can happen if we don't
* do this before the enable bit is cleared. */
BURTC_SyncWait();
BURTC->EN_CLR = BURTC_EN_EN;
#if defined(_BURTC_SYNCBUSY_EN_MASK)
while (BURTC->SYNCBUSY != 0U) {
// Wait for the EN=0 to synchronize
}
#elif defined(_BURTC_EN_DISABLING_MASK)
while (BURTC->EN & _BURTC_EN_DISABLING_MASK) {
/* Wait for disabling to finish */
}
#endif
BURTC->CFG = _BURTC_CFG_RESETVALUE;
#endif
}
#if defined(_BURTC_CTRL_MASK)
/***************************************************************************//**
* @brief
* Get the clock frequency of the BURTC.
*
* @return
* The current frequency in Hz.
******************************************************************************/
uint32_t BURTC_ClockFreqGet(void)
{
uint32_t clkSel;
uint32_t clkDiv;
uint32_t frequency;
clkSel = BURTC->CTRL & _BURTC_CTRL_CLKSEL_MASK;
clkDiv = (BURTC->CTRL & _BURTC_CTRL_PRESC_MASK) >> _BURTC_CTRL_PRESC_SHIFT;
switch (clkSel) {
/** Ultra-low frequency (1 kHz) clock. */
case BURTC_CTRL_CLKSEL_ULFRCO:
if (_BURTC_CTRL_PRESC_DIV1 == clkDiv) {
frequency = 2000; /* 2 kHz when clock divisor is 1. */
} else {
frequency = SystemULFRCOClockGet(); /* 1 kHz when divisor is different
from 1. */
}
break;
/** Low-frequency RC oscillator. */
case BURTC_CTRL_CLKSEL_LFRCO:
frequency = SystemLFRCOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */
break;
/** Low-frequency crystal oscillator. */
case BURTC_CTRL_CLKSEL_LFXO:
frequency = SystemLFXOClockGet() / (1 << clkDiv); /* freq=32768/2^clkDiv */
break;
default:
/* No clock selected for BURTC. */
frequency = 0;
}
return frequency;
}
#endif
/** @} (end addtogroup burtc) */
#endif /* BURTC_PRESENT */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,518 @@
/***************************************************************************//**
* @file
* @brief Core interrupt handling API
*******************************************************************************
* # 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_core.h"
#include "sl_assert.h"
/* *INDENT-OFF* */
// *****************************************************************************
/// @addtogroup core CORE - Interrupt Handling
///
///
/// @warning
/// If you are looking for atomic and critical sections, they have been moved
/// to platform/common in the Core API.
///
/// @section core_intro Introduction
///
/// This module provides support for NVIC sections. NVIC sections are like
/// critical sections, except interrupts are masked on an individual IRQ basis.
/// This module also provides an API to relocate the vector table in RAM, and
/// register IRQ handlers in the RAM based interrupt vector table.
///
/// @li <b>NVIC mask section</b>: Mask interrupts (external interrupts) on an
/// individual IRQ basis.
///
/// @section core_examples Examples
///
/// Implement an NVIC critical section:
/// @code{.c}
/// {
/// CORE_DECLARE_NVIC_ZEROMASK(mask); // A zero initialized NVIC disable mask
///
/// // Set mask bits for IRQs to block in the NVIC critical section.
/// // In many cases, you can create the disable mask once upon application
/// // startup and use the mask globally throughout the application lifetime.
/// CORE_NvicMaskSetIRQ(LEUART0_IRQn, &mask);
/// CORE_NvicMaskSetIRQ(VCMP_IRQn, &mask);
///
/// // Enter NVIC critical section with the disable mask
/// CORE_NVIC_SECTION(&mask,
/// ...
/// ... your code goes here ...
/// ...
/// )
/// }
/// @endcode
///
/// @section core_vector_tables Interrupt vector tables
///
/// When using RAM based interrupt vector tables it is the user's responsibility
/// to allocate the table space correctly. The tables must be aligned as specified
/// in the CPU reference manual.
///
/// Use @ref CORE_InitNvicVectorTable() to initialize a RAM based vector table
/// by copying table entries from a source vector table to a target table.
/// VTOR is set to the address of the target vector table.
///
/// Use @ref CORE_GetNvicRamTableHandler() @ref CORE_SetNvicRamTableHandler()
/// to get or set the interrupt handler for a specific IRQn. They both use
/// the interrupt vector table defined by the current VTOR register value.
///
/// @{
// *****************************************************************************
/* *INDENT-ON* */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Enter a NVIC mask section.
*
* When a NVIC mask section is entered, specified NVIC interrupts
* are disabled.
*
* @deprecated Will be removed from the Simplicity SDK
*
* @param[out] nvicState
* Return NVIC interrupts enable mask prior to section entry.
*
* @param[in] disable
* A mask specifying which NVIC interrupts to disable within the section.
******************************************************************************/
void CORE_EnterNvicMask(CORE_nvicMask_t *nvicState,
const CORE_nvicMask_t *disable)
{
CORE_CRITICAL_SECTION(
*nvicState = *(CORE_nvicMask_t*)((uint32_t)&NVIC->ICER[0]);
*(CORE_nvicMask_t*)((uint32_t)&NVIC->ICER[0]) = *disable;
)
}
/***************************************************************************//**
* @brief
* Disable NVIC interrupts.
*
* @deprecated Will be removed from the Simplicity SDK
*
* @param[in] disable
* A mask specifying which NVIC interrupts to disable.
******************************************************************************/
void CORE_NvicDisableMask(const CORE_nvicMask_t *disable)
{
CORE_CRITICAL_SECTION(
*(CORE_nvicMask_t*)((uint32_t)&NVIC->ICER[0]) = *disable;
)
}
/***************************************************************************//**
* @brief
* Set current NVIC interrupt enable mask.
*
* @deprecated Will be removed from the Simplicity SDK
*
* @param[out] enable
* A mask specifying which NVIC interrupts are currently enabled.
******************************************************************************/
void CORE_NvicEnableMask(const CORE_nvicMask_t *enable)
{
CORE_CRITICAL_SECTION(
*(CORE_nvicMask_t*)((uint32_t)&NVIC->ISER[0]) = *enable;
)
}
/***************************************************************************//**
* @brief
* Brief NVIC interrupt enable/disable sequence to allow handling of
* pending interrupts.
*
* @deprecated Will be removed from the Simplicity SDK
*
* @param[in] enable
* A mask specifying which NVIC interrupts to briefly enable.
*
* @note
* Usually used within an NVIC mask section.
******************************************************************************/
void CORE_YieldNvicMask(const CORE_nvicMask_t *enable)
{
CORE_nvicMask_t nvicMask;
// Get current NVIC enable mask.
CORE_CRITICAL_SECTION(
nvicMask = *(CORE_nvicMask_t*)((uint32_t)&NVIC->ISER[0]);
)
// Make a mask with bits set for those interrupts that are currently
// disabled but are set in the enable mask.
#if (CORE_NVIC_REG_WORDS == 1)
nvicMask.a[0] &= enable->a[0];
nvicMask.a[0] = ~nvicMask.a[0] & enable->a[0];
if (nvicMask.a[0] != 0) {
#elif (CORE_NVIC_REG_WORDS == 2)
nvicMask.a[0] &= enable->a[0];
nvicMask.a[1] &= enable->a[1];
nvicMask.a[0] = ~nvicMask.a[0] & enable->a[0];
nvicMask.a[1] = ~nvicMask.a[1] & enable->a[1];
if ((nvicMask.a[0] != 0U) || (nvicMask.a[1] != 0U)) {
#elif (CORE_NVIC_REG_WORDS == 3)
nvicMask.a[0] &= enable->a[0];
nvicMask.a[1] &= enable->a[1];
nvicMask.a[2] &= enable->a[2];
nvicMask.a[0] = ~nvicMask.a[0] & enable->a[0];
nvicMask.a[1] = ~nvicMask.a[1] & enable->a[1];
nvicMask.a[2] = ~nvicMask.a[2] & enable->a[2];
if ((nvicMask.a[0] != 0U) || (nvicMask.a[1] != 0U) || (nvicMask.a[2] != 0U)) {
#endif
// Enable previously disabled interrupts.
*(CORE_nvicMask_t*)((uint32_t)&NVIC->ISER[0]) = nvicMask;
// Disable those interrupts again.
*(CORE_nvicMask_t*)((uint32_t)&NVIC->ICER[0]) = nvicMask;
}
}
/***************************************************************************//**
* @brief
* Utility function to set an IRQn bit in a NVIC enable/disable mask.
*
* @deprecated Will be removed from the Simplicity SDK
* Use sl_interrupt_manager_enable from the interrupt_manager service to replace
* the calls to this API. Note that each interrupts will need to be enabled
* individually instead of using a mask.
*
* @param[in] irqN
* The IRQn_Type enumerator for the interrupt.
*
* @param[in,out] mask
* The mask to set the interrupt bit in.
******************************************************************************/
void CORE_NvicMaskSetIRQ(IRQn_Type irqN, CORE_nvicMask_t *mask)
{
EFM_ASSERT(((int)irqN >= 0) && ((int)irqN < EXT_IRQ_COUNT));
mask->a[(unsigned)irqN >> 5] |= 1UL << ((unsigned)irqN & 0x1FUL);
}
/***************************************************************************//**
* @brief
* Utility function to clear an IRQn bit in a NVIC enable/disable mask.
*
* @deprecated Will be removed from the Simplicity SDK
* Use sl_interrupt_manager_enable from the interrupt_manager service to replace
* the calls to this API. Note that each interrupts will need to be disabled
* individually instead of using a mask.
*
* @param[in] irqN
* The IRQn_Type enumerator for the interrupt.
*
* @param[in,out] mask
* The mask to clear the interrupt bit in.
******************************************************************************/
void CORE_NvicMaskClearIRQ(IRQn_Type irqN, CORE_nvicMask_t *mask)
{
EFM_ASSERT(((int)irqN >= 0) && ((int)irqN < EXT_IRQ_COUNT));
mask->a[(unsigned)irqN >> 5] &= ~(1UL << ((unsigned)irqN & 0x1FUL));
}
/***************************************************************************//**
* @brief
* Get the current NVIC enable mask state.
*
* @deprecated Will be removed from the Simplicity SDK
*
* @param[out] mask
* The current NVIC enable mask.
******************************************************************************/
void CORE_GetNvicEnabledMask(CORE_nvicMask_t *mask)
{
CORE_CRITICAL_SECTION(
*mask = *(CORE_nvicMask_t*)((uint32_t)&NVIC->ISER[0]);
)
}
/***************************************************************************//**
* @brief
* Get NVIC disable state for a given mask.
*
* @deprecated Will be removed from the Simplicity SDK
*
* @param[in] mask
* An NVIC mask to check.
*
* @return
* True if all NVIC interrupt mask bits are clear.
******************************************************************************/
bool CORE_GetNvicMaskDisableState(const CORE_nvicMask_t *mask)
{
CORE_nvicMask_t nvicMask;
CORE_CRITICAL_SECTION(
nvicMask = *(CORE_nvicMask_t*)((uint32_t)&NVIC->ISER[0]);
)
#if (CORE_NVIC_REG_WORDS == 1)
return (mask->a[0] & nvicMask.a[0]) == 0U;
#elif (CORE_NVIC_REG_WORDS == 2)
return ((mask->a[0] & nvicMask.a[0]) == 0U)
&& ((mask->a[1] & nvicMask.a[1]) == 0U);
#elif (CORE_NVIC_REG_WORDS == 3)
return ((mask->a[0] & nvicMask.a[0]) == 0U)
&& ((mask->a[1] & nvicMask.a[1]) == 0U)
&& ((mask->a[2] & nvicMask.a[2]) == 0U);
#endif
}
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/***************************************************************************//**
* @brief Internal function to query the state an IRQ. Present for compatibility
* during the deprecation process.
*
* @param[in] irqN IRQ number.
*
* @return True of False depending on if the IRQ is active in the NVIC or not.
******************************************************************************/
__STATIC_INLINE bool internal_NvicIRQDisabled(IRQn_Type irqN)
{
CORE_nvicMask_t *mask;
EFM_ASSERT(((int)irqN >= 0) && ((int)irqN < EXT_IRQ_COUNT));
mask = (CORE_nvicMask_t*)((uint32_t)&NVIC->ISER[0]);
return (mask->a[(unsigned)irqN >> 5U] & (1UL << ((unsigned)irqN & 0x1FUL)))
== 0UL;
}
/** @endcond */
/***************************************************************************//**
* @brief
* Check if an NVIC interrupt is disabled.
*
* @deprecated Will be removed from the Simplicity SDK, please now use the
* function sl_interrupt_manager_is_irq_disabled in the interrupt manager
* service component.
*
* @param[in] irqN
* The IRQn_Type enumerator for the interrupt to check.
*
* @return
* True if the interrupt is disabled.
******************************************************************************/
bool CORE_NvicIRQDisabled(IRQn_Type irqN)
{
return internal_NvicIRQDisabled(irqN);
}
/***************************************************************************//**
* @brief
* Utility function to get the handler for a specific interrupt.
*
* @deprecated Will be removed from the Simplicity SDK
*
* @param[in] irqN
* The IRQn_Type enumerator for the interrupt.
*
* @return
* The handler address.
*
* @note
* Uses the interrupt vector table defined by the current VTOR register value.
******************************************************************************/
void *CORE_GetNvicRamTableHandler(IRQn_Type irqN)
{
EFM_ASSERT(((int)irqN >= -16) && ((int)irqN < EXT_IRQ_COUNT));
return (void*)((uint32_t*)(((uint32_t*)SCB->VTOR)[(int)irqN + 16]));
}
/***************************************************************************//**
* @brief
* Utility function to set the handler for a specific interrupt.
*
* @deprecated Will be removed from the Simplicity SDK, please now use the
* function sl_interrupt_manager_set_irq_handler in the interrupt manager
* service component.
*
* @param[in] irqN
* The IRQn_Type enumerator for the interrupt.
*
* @param[in] handler
* The handler address.
*
* @note
* Uses the interrupt vector table defined by the current VTOR register value.
******************************************************************************/
void CORE_SetNvicRamTableHandler(IRQn_Type irqN, void *handler)
{
EFM_ASSERT(((int)irqN >= -16) && ((int)irqN < EXT_IRQ_COUNT));
((uint32_t*)SCB->VTOR)[(int)irqN + 16] = (uint32_t)((uint32_t*)handler);
}
/***************************************************************************//**
* @brief
* Initialize an interrupt vector table by copying table entries from a
* source to a target table.
*
* @note This function will set a new VTOR register value.
*
* @param[in] sourceTable
* The address of the source vector table.
*
* @param[in] sourceSize
* A number of entries in the source vector table.
*
* @param[in] targetTable
* The address of the target (new) vector table.
*
* @param[in] targetSize
* A number of entries in the target vector table.
*
* @param[in] defaultHandler
* An address of the interrupt handler used for target entries for which where there
* is no corresponding source entry (i.e., the target table is larger than the source
* table).
*
* @param[in] overwriteActive
* When true, a target table entry is always overwritten with the
* corresponding source entry. If false, a target table entry is only
* overwritten if it is zero. This makes it possible for an application
* to partly initialize a target table before passing it to this function.
*
******************************************************************************/
void CORE_InitNvicVectorTable(uint32_t *sourceTable,
uint32_t sourceSize,
uint32_t *targetTable,
uint32_t targetSize,
void *defaultHandler,
bool overwriteActive)
{
uint32_t i;
// ASSERT on non SRAM-based target table.
EFM_ASSERT(((uint32_t)targetTable >= SRAM_BASE)
&& ((uint32_t)targetTable < (SRAM_BASE + SRAM_SIZE)));
// ASSERT if misaligned with respect to the VTOR register implementation.
#if defined(SCB_VTOR_TBLBASE_Msk)
EFM_ASSERT(((uint32_t)targetTable & ~(SCB_VTOR_TBLOFF_Msk
| SCB_VTOR_TBLBASE_Msk)) == 0U);
#else
EFM_ASSERT(((uint32_t)targetTable & ~SCB_VTOR_TBLOFF_Msk) == 0U);
#endif
// ASSERT if misaligned with respect to the vector table size.
// The vector table address must be aligned at its size rounded up to nearest 2^n.
EFM_ASSERT(((uint32_t)targetTable
& ((1UL << (32UL - __CLZ((targetSize * 4UL) - 1UL))) - 1UL))
== 0UL);
for (i = 0; i < targetSize; i++) {
if (overwriteActive) { // Overwrite target entries.
if (i < sourceSize) { // targetSize <= sourceSize
targetTable[i] = sourceTable[i];
} else { // targetSize > sourceSize
targetTable[i] = (uint32_t)((uint32_t*)defaultHandler);
}
} else { // Overwrite target entries which are 0.
if (i < sourceSize) { // targetSize <= sourceSize
if (targetTable[i] == 0U) {
targetTable[i] = sourceTable[i];
}
} else { // targetSize > sourceSize
if (targetTable[i] == 0U) {
targetTable[i] = (uint32_t)((uint32_t*)defaultHandler);
}
}
}
}
SCB->VTOR = (uint32_t)targetTable;
}
/***************************************************************************//**
* @brief
* Check if a specific interrupt is disabled or blocked.
*
* @deprecated Will be removed from the Simplicity SDK, please now use the
* function sl_interrupt_manager_is_irq_blocked in the interrupt manager
* service component.
*
* @param[in] irqN
* The IRQn_Type enumerator for the interrupt to check.
*
* @return
* True if the interrupt is disabled or blocked.
******************************************************************************/
SL_WEAK bool CORE_IrqIsBlocked(IRQn_Type irqN)
{
uint32_t irqPri, activeIrq;
#if (__CORTEX_M >= 3)
uint32_t basepri;
EFM_ASSERT((irqN >= MemoryManagement_IRQn)
&& (irqN < (IRQn_Type)EXT_IRQ_COUNT));
#else
EFM_ASSERT((irqN >= SVCall_IRQn) && ((IRQn_Type)irqN < EXT_IRQ_COUNT));
#endif
if ((__get_PRIMASK() & 1U) != 0U) {
return true; // All IRQs are disabled.
}
if (internal_NvicIRQDisabled(irqN)) {
return true; // The IRQ in question is disabled.
}
irqPri = NVIC_GetPriority(irqN);
#if (__CORTEX_M >= 3)
basepri = __get_BASEPRI();
if ((basepri != 0U)
&& (irqPri >= (basepri >> (8U - __NVIC_PRIO_BITS)))) {
return true; // The IRQ in question has too low
} // priority vs. BASEPRI.
#endif
// Check if already in an interrupt handler. If so, an interrupt with a
// higher priority (lower priority value) can preempt.
activeIrq = (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) >> SCB_ICSR_VECTACTIVE_Pos;
if (activeIrq != 0U) {
if (irqPri >= NVIC_GetPriority((IRQn_Type)(activeIrq - 16U))) {
return true; // The IRQ in question has too low
} // priority vs. current active IRQ
}
return false;
}
/** @} (end addtogroup core) */

View File

@@ -0,0 +1,132 @@
/***************************************************************************//**
* @file
* @brief Debug (DBG) Peripheral API
*******************************************************************************
* # 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_dbg.h"
#if defined(CoreDebug_DHCSR_C_DEBUGEN_Msk)
#include "sl_assert.h"
#include "em_cmu.h"
#include "em_gpio.h"
#include "em_msc.h"
/***************************************************************************//**
* @addtogroup dbg DBG - Debug
* @brief Debug (DBG) Peripheral API
* @details
* This module contains functions to control the DBG peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The Debug Interface is used to program and debug
* Silicon Labs devices.
* @{
******************************************************************************/
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
#if defined(GPIO_ROUTE_SWOPEN) || defined(GPIO_ROUTEPEN_SWVPEN) \
|| defined(GPIO_TRACEROUTEPEN_SWVPEN)
/***************************************************************************//**
* @brief
* Enable Serial Wire Output (SWO) pin.
*
* @details
* The SWO pin (sometimes denoted SWV, serial wire viewer) allows for
* miscellaneous output to be passed from the Cortex-M3 debug trace module to
* an external debug probe. By default, the debug trace module and pin output
* may be disabled.
*
* Since the SWO pin is only useful when using a debugger, a suggested use
* of this function during startup may be:
* @verbatim
* if (DBG_Connected())
* {
* DBG_SWOEnable(1);
* }
* @endverbatim
* By checking if the debugger is attached, a setup leading to a higher energy
* consumption when the debugger is attached can be avoided when not using
* a debugger.
*
* Another alternative may be to set the debugger tool chain to configure
* the required setup (similar to the content of this function) by some
* sort of toolchain scripting during its attach/reset procedure. In that
* case, the above suggested code for enabling the SWO pin is not required
* in the application.
*
* @param[in] location
* A pin location used for SWO pin on the application in use.
******************************************************************************/
void DBG_SWOEnable(unsigned int location)
{
int port;
int pin;
#if defined(GPIO_SWV_PORT)
port = GPIO_SWV_PORT;
pin = GPIO_SWV_PIN;
#else
EFM_ASSERT(location < AFCHANLOC_MAX);
#if defined (AF_DBG_SWO_PORT)
port = AF_DBG_SWO_PORT(location);
pin = AF_DBG_SWO_PIN(location);
#elif defined (AF_DBG_SWV_PORT)
port = AF_DBG_SWV_PORT(location);
pin = AF_DBG_SWV_PIN(location);
#else
#warning "AF debug port is not defined."
#endif
#endif
/* Port/pin location not defined for the device. */
if ((pin < 0) || (port < 0)) {
EFM_ASSERT(0);
return;
}
/* Ensure that the auxiliary clock going to the Cortex debug trace module is enabled. */
#if !defined(_SILICON_LABS_32B_SERIES_2)
CMU_OscillatorEnable(cmuOsc_AUXHFRCO, true, false);
#endif
/* Set the selected pin location for the SWO pin and enable it. */
GPIO_DbgLocationSet(location);
GPIO_DbgSWOEnable(true);
/* Configure the SWO pin for output. */
GPIO_PinModeSet((GPIO_Port_TypeDef)port, pin, gpioModePushPull, 0);
}
#endif
/** @} (end addtogroup dbg) */
#endif /* defined( CoreDebug_DHCSR_C_DEBUGEN_Msk ) */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
/***************************************************************************//**
* @file
* @brief General Purpose Cyclic Redundancy Check (GPCRC) API.
*******************************************************************************
* # 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 "sl_common.h"
#include "em_gpcrc.h"
#include "sl_assert.h"
#if defined(GPCRC_PRESENT) && (GPCRC_COUNT > 0)
/***************************************************************************//**
* @addtogroup gpcrc
* @{
******************************************************************************/
/*******************************************************************************
*************************** GLOBAL FUNCTIONS ******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Initialize the General Purpose Cyclic Redundancy Check (GPCRC) module.
*
* @details
* Use this function to configure the operational parameters of the GPCRC,
* such as the polynomial to use and how the input should be preprocessed
* before entering the CRC calculation.
*
* @note
* This function will not copy the initialization value to the data register
* to prepare for a new CRC calculation. Either call
* @ref GPCRC_Start before each calculation or by use the
* autoInit functionality.
*
* @param[in] gpcrc
* A pointer to the GPCRC peripheral register block.
*
* @param[in] init
* A pointer to the initialization structure used to configure the GPCRC.
******************************************************************************/
void GPCRC_Init(GPCRC_TypeDef * gpcrc, const GPCRC_Init_TypeDef * init)
{
uint32_t polySelect;
uint32_t revPoly = 0;
if (init->crcPoly == 0x04C11DB7) {
polySelect = GPCRC_CTRL_POLYSEL_CRC32;
} else {
// If not using the fixed CRC-32 polynomial, use 16-bit.
EFM_ASSERT((init->crcPoly & 0xFFFF0000UL) == 0U);
#if defined(GPCRC_CTRL_POLYSEL_CRC16)
polySelect = GPCRC_CTRL_POLYSEL_CRC16;
#else
polySelect = GPCRC_CTRL_POLYSEL_16;
#endif
revPoly = SL_RBIT16(init->crcPoly);
}
#if defined(GPCRC_EN_EN)
if (init->enable) {
gpcrc->EN_SET = GPCRC_EN_EN;
} else {
gpcrc->EN_CLR = GPCRC_EN_EN;
}
gpcrc->CTRL = (((uint32_t)init->autoInit << _GPCRC_CTRL_AUTOINIT_SHIFT)
| ((uint32_t)init->reverseByteOrder << _GPCRC_CTRL_BYTEREVERSE_SHIFT)
| ((uint32_t)init->reverseBits << _GPCRC_CTRL_BITREVERSE_SHIFT)
| ((uint32_t)init->enableByteMode << _GPCRC_CTRL_BYTEMODE_SHIFT)
| polySelect);
#else
gpcrc->CTRL = (((uint32_t)init->autoInit << _GPCRC_CTRL_AUTOINIT_SHIFT)
| ((uint32_t)init->reverseByteOrder << _GPCRC_CTRL_BYTEREVERSE_SHIFT)
| ((uint32_t)init->reverseBits << _GPCRC_CTRL_BITREVERSE_SHIFT)
| ((uint32_t)init->enableByteMode << _GPCRC_CTRL_BYTEMODE_SHIFT)
| polySelect
| ((uint32_t)init->enable << _GPCRC_CTRL_EN_SHIFT));
#endif
#if defined(GPCRC_CTRL_POLYSEL_CRC16)
if (polySelect == GPCRC_CTRL_POLYSEL_CRC16) {
#else
if (polySelect == GPCRC_CTRL_POLYSEL_16) {
#endif
// Set the CRC polynomial value.
gpcrc->POLY = revPoly & _GPCRC_POLY_POLY_MASK;
}
// Load the CRC initialization value to GPCRC_INIT.
gpcrc->INIT = init->initValue;
}
/***************************************************************************//**
* @brief
* Reset GPCRC registers to the hardware reset state.
*
* @note
* The data registers are not reset by this function.
*
* @param[in] gpcrc
* A pointer to the GPCRC peripheral register block.
******************************************************************************/
void GPCRC_Reset(GPCRC_TypeDef * gpcrc)
{
gpcrc->CTRL = _GPCRC_CTRL_RESETVALUE;
gpcrc->POLY = _GPCRC_POLY_RESETVALUE;
gpcrc->INIT = _GPCRC_INIT_RESETVALUE;
}
/** @} (end addtogroup gpcrc) */
#endif /* defined(GPCRC_COUNT) && (GPCRC_COUNT > 0) */

View File

@@ -0,0 +1,452 @@
/***************************************************************************//**
* @file
* @brief General Purpose IO (GPIO) peripheral API
* devices.
*******************************************************************************
* # 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_gpio.h"
#if defined(GPIO_COUNT) && (GPIO_COUNT > 0)
/***************************************************************************//**
* @addtogroup gpio GPIO - General Purpose Input/Output
* @brief General Purpose Input/Output (GPIO) API
* @details
* This module contains functions to control the GPIO peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The GPIO peripheral is used for pin configuration
* and direct pin manipulation and sensing as well as routing for peripheral
* pin connections.
* @{
******************************************************************************/
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/** Validation of the pin typically usable in assert statements. */
#define GPIO_DRIVEMODE_VALID(mode) ((mode) <= 3)
#define GPIO_STRENGTH_VALID(strength) (!((strength) \
& ~(_GPIO_P_CTRL_DRIVESTRENGTH_MASK \
| _GPIO_P_CTRL_DRIVESTRENGTHALT_MASK)))
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Sets the pin location of the debug pins (Serial Wire interface).
*
* @note
* Changing the pins used for debugging uncontrolled, may result in a lockout.
*
* @param[in] location
* The debug pin location to use (0-3).
******************************************************************************/
void GPIO_DbgLocationSet(unsigned int location)
{
#if defined (_GPIO_ROUTE_SWLOCATION_MASK)
EFM_ASSERT(location < AFCHANLOC_MAX);
GPIO->ROUTE = (GPIO->ROUTE & ~_GPIO_ROUTE_SWLOCATION_MASK)
| (location << _GPIO_ROUTE_SWLOCATION_SHIFT);
#elif defined (_GPIO_ROUTELOC0_SWVLOC_MASK)
EFM_ASSERT(location < AFCHANLOC_MAX);
GPIO->ROUTELOC0 = (GPIO->ROUTELOC0 & ~_GPIO_ROUTELOC0_SWVLOC_MASK)
| (location << _GPIO_ROUTELOC0_SWVLOC_SHIFT);
#else
(void)location;
#endif
}
#if defined (_GPIO_P_CTRL_DRIVEMODE_MASK)
/***************************************************************************//**
* @brief
* Sets drive mode for a GPIO port.
*
* @param[in] port
* The GPIO port to access.
*
* @param[in] mode
* Drive mode to use for the port.
******************************************************************************/
void GPIO_DriveModeSet(GPIO_Port_TypeDef port, GPIO_DriveMode_TypeDef mode)
{
EFM_ASSERT(GPIO_PORT_VALID(port) && GPIO_DRIVEMODE_VALID(mode));
GPIO->P[port].CTRL = (GPIO->P[port].CTRL & ~(_GPIO_P_CTRL_DRIVEMODE_MASK))
| (mode << _GPIO_P_CTRL_DRIVEMODE_SHIFT);
}
#endif
#if defined (_GPIO_P_CTRL_DRIVESTRENGTH_MASK)
/***************************************************************************//**
* @brief
* Sets the drive strength for a GPIO port.
*
* @param[in] port
* The GPIO port to access.
*
* @param[in] strength
* The drive strength to use for the port.
******************************************************************************/
void GPIO_DriveStrengthSet(GPIO_Port_TypeDef port,
GPIO_DriveStrength_TypeDef strength)
{
EFM_ASSERT(GPIO_PORT_VALID(port) && GPIO_STRENGTH_VALID(strength));
BUS_RegMaskedWrite(&GPIO->P[port].CTRL,
_GPIO_P_CTRL_DRIVESTRENGTH_MASK | _GPIO_P_CTRL_DRIVESTRENGTHALT_MASK,
strength);
}
#endif
/***************************************************************************//**
* @brief
* Configure the GPIO external pin interrupt.
*
* @details
* It is recommended to disable interrupts before configuring the GPIO pin interrupt.
* See @ref GPIO_IntDisable() for more information.
*
* The GPIO interrupt handler must be in place before enabling the
* interrupt.
*
* Notice that any pending interrupt for the selected interrupt is cleared
* by this function.
*
* @note
* On series 0 devices, the pin number parameter is not used. The
* pin number used on these devices is hardwired to the interrupt with the
* same number. @n
* On series 1 devices, the pin number can be selected freely within a group.
* Interrupt numbers are divided into 4 groups (intNo / 4) and valid pin
* number within the interrupt groups are:
* 0: pins 0-3 (interrupt number 0-3)
* 1: pins 4-7 (interrupt number 4-7)
* 2: pins 8-11 (interrupt number 8-11)
* 3: pins 12-15 (interrupt number 12-15)
*
* @param[in] port
* The port to associate with the @p pin.
*
* @param[in] pin
* The pin number on the port.
*
* @param[in] intNo
* The interrupt number to trigger.
*
* @param[in] risingEdge
* Set to true if the interrupt will be enabled on the rising edge. Otherwise, false.
*
* @param[in] fallingEdge
* Set to true if the interrupt will be enabled on the falling edge. Otherwise, false.
*
* @param[in] enable
* Set to true if the interrupt will be enabled after the configuration is complete.
* False to leave disabled. See @ref GPIO_IntDisable() and @ref GPIO_IntEnable().
******************************************************************************/
void GPIO_ExtIntConfig(GPIO_Port_TypeDef port,
unsigned int pin,
unsigned int intNo,
bool risingEdge,
bool fallingEdge,
bool enable)
{
#if defined (_GPIO_EXTIPSELH_MASK)
uint32_t tmp = 0;
#endif
#if !defined(_GPIO_EXTIPINSELL_MASK)
(void)pin;
#endif
EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin));
#if defined(_GPIO_EXTIPINSELL_MASK)
EFM_ASSERT(GPIO_INTNO_PIN_VALID(intNo, pin));
#endif
/* The EXTIPSELL register controls pins 0-7 and EXTIPSELH controls
* pins 8-15 of the interrupt configuration. */
if (intNo < 8) {
BUS_RegMaskedWrite(&GPIO->EXTIPSELL,
_GPIO_EXTIPSELL_EXTIPSEL0_MASK
<< (_GPIO_EXTIPSELL_EXTIPSEL1_SHIFT * intNo),
(uint32_t)port << (_GPIO_EXTIPSELL_EXTIPSEL1_SHIFT * intNo));
} else {
#if defined(_GPIO_EXTIPSELH_MASK)
tmp = intNo - 8;
#if defined(_GPIO_EXTIPSELH_EXTIPSEL0_MASK)
BUS_RegMaskedWrite(&GPIO->EXTIPSELH,
_GPIO_EXTIPSELH_EXTIPSEL0_MASK
<< (_GPIO_EXTIPSELH_EXTIPSEL1_SHIFT * tmp),
(uint32_t)port << (_GPIO_EXTIPSELH_EXTIPSEL1_SHIFT * tmp));
#elif defined(_GPIO_EXTIPSELH_EXTIPSEL8_MASK)
BUS_RegMaskedWrite(&GPIO->EXTIPSELH,
_GPIO_EXTIPSELH_EXTIPSEL8_MASK
<< (_GPIO_EXTIPSELH_EXTIPSEL9_SHIFT * tmp),
(uint32_t)port << (_GPIO_EXTIPSELH_EXTIPSEL9_SHIFT * tmp));
#else
#error Invalid GPIO_EXTIPINSELH bit fields
#endif
#endif /* #if defined(_GPIO_EXTIPSELH_MASK) */
}
#if defined(_GPIO_EXTIPINSELL_MASK)
/* The EXTIPINSELL register controls interrupt 0-7 and EXTIPINSELH controls
* interrupt 8-15 of the interrupt/pin number mapping. */
if (intNo < 8) {
BUS_RegMaskedWrite(&GPIO->EXTIPINSELL,
_GPIO_EXTIPINSELL_EXTIPINSEL0_MASK
<< (_GPIO_EXTIPINSELL_EXTIPINSEL1_SHIFT * intNo),
(uint32_t)((pin % 4) & _GPIO_EXTIPINSELL_EXTIPINSEL0_MASK)
<< (_GPIO_EXTIPINSELL_EXTIPINSEL1_SHIFT * intNo));
} else {
#if defined (_GPIO_EXTIPINSELH_EXTIPINSEL8_MASK)
BUS_RegMaskedWrite(&GPIO->EXTIPINSELH,
_GPIO_EXTIPINSELH_EXTIPINSEL8_MASK
<< (_GPIO_EXTIPINSELH_EXTIPINSEL9_SHIFT * tmp),
(uint32_t)((pin % 4) & _GPIO_EXTIPINSELH_EXTIPINSEL8_MASK)
<< (_GPIO_EXTIPSELH_EXTIPSEL9_SHIFT * tmp));
#endif
#if defined (_GPIO_EXTIPINSELH_EXTIPINSEL0_MASK)
BUS_RegMaskedWrite(&GPIO->EXTIPINSELH,
_GPIO_EXTIPINSELH_EXTIPINSEL0_MASK
<< (_GPIO_EXTIPINSELH_EXTIPINSEL1_SHIFT * tmp),
(uint32_t)((pin % 4) & _GPIO_EXTIPINSELH_EXTIPINSEL0_MASK)
<< (_GPIO_EXTIPSELH_EXTIPSEL1_SHIFT * tmp));
#endif
}
#endif
/* Enable/disable the rising edge interrupt. */
BUS_RegBitWrite(&(GPIO->EXTIRISE), intNo, risingEdge);
/* Enable/disable the falling edge interrupt. */
BUS_RegBitWrite(&(GPIO->EXTIFALL), intNo, fallingEdge);
/* Clear any pending interrupt. */
GPIO_IntClear(1 << intNo);
/* Finally enable/disable interrupt. */
BUS_RegBitWrite(&(GPIO->IEN), intNo, enable);
}
#if _SILICON_LABS_32B_SERIES > 0
/***************************************************************************//**
* @brief
* Configure EM4WU pins as external level-sensitive interrupts.
*
* @details
* It is recommended to disable interrupts before configuring the GPIO pin interrupt.
* See @ref GPIO_IntDisable() for more information.
*
* The GPIO interrupt handler must be in place before enabling the
* interrupt.
*
* Notice that any pending interrupt for the selected interrupt is cleared
* by this function.
*
* @note
* The selected port/pin must be mapped to an existant EM4WU interrupt.
* Each EM4WU signal is connected to a fixed pin.
* Refer to the Alternate Function Table in the device Datasheet for the
* location of each EM4WU signal. For example, on xG22 device, the interrupt
* of EM4WU6 is fixed to pin PC00.
*
* @param[in] port
* The port to associate with the @p pin.
*
* @param[in] pin
* The pin number on the port.
*
* @param[in] intNo
* The EM4WU interrupt number to trigger.
*
* @param[in] polarity
* true = Active high level-sensitive interrupt.
* false = Active low level-sensitive interrupt.
*
* @param[in] enable
* Set to true if the interrupt will be enabled after the configuration is complete.
* False to leave disabled. See @ref GPIO_IntDisable() and @ref GPIO_IntEnable().
******************************************************************************/
void GPIO_EM4WUExtIntConfig(GPIO_Port_TypeDef port,
unsigned int pin,
uint32_t intNo,
bool polarity,
bool enable)
{
EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin));
// GPIO pin mode set.
GPIO_PinModeSet(port, pin, gpioModeInputPullFilter, (unsigned int)!polarity);
// Enable EM4WU function and set polarity
uint32_t polarityMask = (uint32_t)polarity << (intNo + _GPIO_EM4WUEN_EM4WUEN_SHIFT);
uint32_t pinmask = 1UL << (intNo + _GPIO_EM4WUEN_EM4WUEN_SHIFT);
GPIO_EM4EnablePinWakeup(pinmask, polarityMask);
// Enable EM4WU interrupt
#if defined(_SILICON_LABS_32B_SERIES_1)
BUS_RegBitWrite(&(GPIO->IEN), intNo + _GPIO_IEN_EM4WU_SHIFT, enable);
#elif defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1)
BUS_RegBitWrite(&(GPIO->IEN), intNo + _GPIO_IEN_EM4WUIEN_SHIFT, enable);
#else
BUS_RegBitWrite(&(GPIO->IEN), intNo + _GPIO_IEN_EM4WUIEN0_SHIFT, enable);
#endif
}
#endif
/***************************************************************************//**
* @brief
* Set the mode for a GPIO pin.
*
* @param[in] port
* The GPIO port to access.
*
* @param[in] pin
* The pin number in the port.
*
* @param[in] mode
* The desired pin mode.
*
* @param[in] out
* A value to set for the pin in the DOUT register. The DOUT setting is important for
* some input mode configurations to determine the pull-up/down direction.
******************************************************************************/
void GPIO_PinModeSet(GPIO_Port_TypeDef port,
unsigned int pin,
GPIO_Mode_TypeDef mode,
unsigned int out)
{
EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin));
/* If disabling a pin, do not modify DOUT to reduce the chance of */
/* a glitch/spike (may not be sufficient precaution in all use cases). */
if (mode != gpioModeDisabled) {
if (out) {
GPIO_PinOutSet(port, pin);
} else {
GPIO_PinOutClear(port, pin);
}
}
/* There are two registers controlling the pins for each port. The MODEL
* register controls pins 0-7 and MODEH controls pins 8-15. */
if (pin < 8) {
// Cast parameter [mode] to 32 bits to fix C99 Undefined Behavior (see SEI CERT C INT34-C)
// Compiler assigned 8 bits for enum. Same thing for other branch.
BUS_RegMaskedWrite(&(GPIO->P[port].MODEL), 0xFu << (pin * 4), (uint32_t)mode << (pin * 4));
} else {
BUS_RegMaskedWrite(&(GPIO->P[port].MODEH), 0xFu << ((pin - 8) * 4), (uint32_t)mode << ((pin - 8) * 4));
}
if (mode == gpioModeDisabled) {
if (out) {
GPIO_PinOutSet(port, pin);
} else {
GPIO_PinOutClear(port, pin);
}
}
}
/***************************************************************************//**
* @brief
* Get the mode for a GPIO pin.
*
* @param[in] port
* The GPIO port to access.
*
* @param[in] pin
* The pin number in the port.
*
* @return
* The pin mode.
******************************************************************************/
GPIO_Mode_TypeDef GPIO_PinModeGet(GPIO_Port_TypeDef port,
unsigned int pin)
{
EFM_ASSERT(GPIO_PORT_PIN_VALID(port, pin));
if (pin < 8) {
return (GPIO_Mode_TypeDef) ((GPIO->P[port].MODEL >> (pin * 4)) & 0xF);
} else {
return (GPIO_Mode_TypeDef) ((GPIO->P[port].MODEH >> ((pin - 8) * 4)) & 0xF);
}
}
#if defined(_GPIO_EM4WUEN_MASK)
/**************************************************************************//**
* @brief
* Enable GPIO pin wake-up from EM4. When the function exits,
* EM4 mode can be safely entered.
*
* @note
* It is assumed that the GPIO pin modes are set correctly.
* Valid modes are @ref gpioModeInput and @ref gpioModeInputPull.
*
* @param[in] pinmask
* A bitmask containing the bitwise logic OR of which GPIO pin(s) to enable.
* See Reference Manuals for a pinmask to the GPIO port/pin mapping.
* @param[in] polaritymask
* A bitmask containing the bitwise logic OR of GPIO pin(s) wake-up polarity.
* See Reference Manuals for pinmask-to-GPIO port/pin mapping.
*****************************************************************************/
void GPIO_EM4EnablePinWakeup(uint32_t pinmask, uint32_t polaritymask)
{
EFM_ASSERT((pinmask & ~_GPIO_EM4WUEN_MASK) == 0);
#if defined(_GPIO_EM4WUPOL_MASK)
EFM_ASSERT((polaritymask & ~_GPIO_EM4WUPOL_MASK) == 0);
GPIO->EM4WUPOL &= ~pinmask; /* Set the wakeup polarity. */
GPIO->EM4WUPOL |= pinmask & polaritymask;
#elif defined(_GPIO_EXTILEVEL_MASK)
EFM_ASSERT((polaritymask & ~_GPIO_EXTILEVEL_MASK) == 0);
GPIO->EXTILEVEL &= ~pinmask;
GPIO->EXTILEVEL |= pinmask & polaritymask;
#endif
GPIO->EM4WUEN |= pinmask; /* Enable wakeup. */
GPIO_EM4SetPinRetention(true); /* Enable the pin retention. */
#if defined(_GPIO_CMD_EM4WUCLR_MASK)
GPIO->CMD = GPIO_CMD_EM4WUCLR; /* Clear the wake-up logic. */
#else
GPIO_IntClear(pinmask);
#endif
}
#endif
/** @} (end addtogroup gpio) */
#endif /* defined(GPIO_COUNT) && (GPIO_COUNT > 0) */

View File

@@ -0,0 +1,940 @@
/***************************************************************************//**
* @file
* @brief Inter-integrated Circuit (I2C) Peripheral API
*******************************************************************************
* # 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_i2c.h"
#if defined(I2C_COUNT) && (I2C_COUNT > 0)
#include "em_cmu.h"
#include "em_bus.h"
#include "sl_assert.h"
#include <limits.h>
/***************************************************************************//**
* @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))
#elif (I2C_COUNT == 4)
#define I2C_REF_VALID(ref) (((ref) == I2C0) || ((ref) == I2C1) || ((ref) == I2C2) || ((ref) == I2C3))
#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)
#if defined(_EFM32_HAPPY_FAMILY) || defined(_EFM32_ZERO_FAMILY)
#define I2C_CR_MAX 8
#else
#define I2C_CR_MAX 4
#endif
#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 = (int32_t)(((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;
bool finished = false;
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
#if (I2C_COUNT > 3)
else if (i2c == I2C3) {
transfer = i2cTransfer + 3;
}
#endif
else {
return i2cTransferUsageFault;
}
seq = transfer->seq;
while (!finished) {
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;
break;
}
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;
finished = true;
break;
/*******************************************************/
/* 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;
}
}
}
finished = true;
break;
/******************************************************/
/* 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;
}
finished = true;
break;
/*******************************/
/* Send a repeated start+address */
/*******************************/
case i2cStateRStartAddrSend:
if (seq->flags & I2C_FLAG_10BIT_ADDR) {
tmp = (uint32_t)((seq->addr >> 8) & 0x06) | 0xf0;
} else {
tmp = (uint32_t)(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;
finished = true;
break;
/**********************************************************************/
/* 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;
}
}
finished = true;
break;
/*****************************/
/* 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;
finished = true;
break;
}
/* 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;
finished = true;
break;
/*********************************************************/
/* 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;
}
finished = true;
break;
/****************************/
/* 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);
#if (defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1) \
|| defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2) \
|| defined(_SILICON_LABS_32B_SERIES_2_CONFIG_3))
// Errata I2C_E303. I2C Fails to Indicate New Incoming Data.
uint32_t status = i2c->STATUS;
// look for invalid RXDATAV = 0 and RXFULL = 1 condition
if (((status & I2C_IF_RXDATAV) == 0) & ((status & I2C_IF_RXFULL) != 0)) {
// Performing a dummy read of the RXFIFO (I2C_RXDATA).
// This restores the expected RXDATAV = 1 and RXFULL = 0 condition.
(void)i2c->RXDATA;
// The dummy read will also set the RXUFIF flag bit, which should be ignored and cleared.
I2C_IntClear(i2c, I2C_IF_RXUF);
}
#endif
/* 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;
}
}
}
finished = true;
break;
/***********************************/
/* Wait for STOP to have been sent */
/***********************************/
case i2cStateWFStopSent:
if (pending & I2C_IF_MSTOP) {
I2C_IntClear(i2c, I2C_IF_MSTOP);
transfer->state = i2cStateDone;
}
finished = true;
break;
/******************************/
/* An unexpected state, software fault */
/******************************/
default:
transfer->result = i2cTransferSwFault;
transfer->state = i2cStateDone;
finished = true;
break;
}
}
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
#if (I2C_COUNT > 3)
else if (i2c == I2C3) {
transfer = i2cTransfer + 3;
}
#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) */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,461 @@
/***************************************************************************//**
* @file
* @brief Direct memory access (LDMA) module peripheral API
*******************************************************************************
* # 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_ldma.h"
#if defined(LDMA_PRESENT) && (LDMA_COUNT == 1)
#include <stddef.h>
#include "sl_assert.h"
#include "em_bus.h"
#include "em_cmu.h"
#include "em_core.h"
/***************************************************************************//**
* @addtogroup ldma
* @{
******************************************************************************/
#if defined(LDMA_IRQ_HANDLER_TEMPLATE)
/***************************************************************************//**
* @brief
* A template for an LDMA IRQ handler.
******************************************************************************/
void LDMA_IRQHandler(void)
{
uint32_t ch;
/* Get all pending and enabled interrupts. */
uint32_t pending = LDMA_IntGetEnabled();
/* Loop on an LDMA error to enable debugging. */
while (pending & LDMA_IF_ERROR) {
}
/* Iterate over all LDMA channels. */
for (ch = 0; ch < DMA_CHAN_COUNT; ch++) {
uint32_t mask = 0x1 << ch;
if (pending & mask) {
/* Clear the interrupt flag. */
LDMA->IFC = mask;
/* Perform more actions here, execute callbacks, and so on. */
}
}
}
#endif
/***************************************************************************//**
* @brief
* De-initialize the LDMA controller.
*
* LDMA interrupts are disabled and the LDMA clock is stopped.
******************************************************************************/
void LDMA_DeInit(void)
{
NVIC_DisableIRQ(LDMA_IRQn);
LDMA->IEN = 0;
#if defined(_LDMA_CHDIS_MASK)
LDMA->CHDIS = _LDMA_CHEN_MASK;
#else
LDMA->CHEN = 0;
#endif
#if defined(LDMA_EN_EN)
LDMA->EN = 0;
#if defined(LDMA_EN_DISABLING)
while (LDMA->EN & _LDMA_EN_DISABLING_MASK) {
}
#endif
#endif
CMU_ClockEnable(cmuClock_LDMA, false);
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG) && (_SILICON_LABS_32B_SERIES_2_CONFIG > 1)
CMU_ClockEnable(cmuClock_LDMAXBAR, false);
#endif
}
/***************************************************************************//**
* @brief
* Enable or disable an LDMA channel request.
*
* @details
* Use this function to enable or disable an LDMA channel request. This will
* prevent the LDMA from proceeding after its current transaction if disabled.
*
* @param[in] ch
* LDMA channel to enable or disable requests.
*
* @param[in] enable
* If 'true', the request will be enabled. If 'false', the request will be disabled.
******************************************************************************/
void LDMA_EnableChannelRequest(int ch, bool enable)
{
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
BUS_RegBitWrite(&LDMA->REQDIS, ch, !enable);
}
/***************************************************************************//**
* @brief
* Initialize the LDMA controller.
*
* @details
* This function will disable all the LDMA channels and enable the LDMA bus
* clock in the CMU. This function will also enable the LDMA IRQ in the NVIC
* and set the LDMA IRQ priority to a user-configurable priority. The LDMA
* interrupt priority is configured using the @ref LDMA_Init_t structure.
*
* @note
* Since this function enables the LDMA IRQ, always add a custom
* LDMA_IRQHandler to the application to handle any interrupts
* from LDMA.
*
* @param[in] init
* A pointer to the initialization structure used to configure the LDMA.
******************************************************************************/
void LDMA_Init(const LDMA_Init_t *init)
{
uint32_t ldmaCtrlVal;
EFM_ASSERT(init != NULL);
EFM_ASSERT(!(((uint32_t)init->ldmaInitCtrlNumFixed << _LDMA_CTRL_NUMFIXED_SHIFT)
& ~_LDMA_CTRL_NUMFIXED_MASK));
EFM_ASSERT(init->ldmaInitIrqPriority < (1 << __NVIC_PRIO_BITS));
CMU_ClockEnable(cmuClock_LDMA, true);
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG) && (_SILICON_LABS_32B_SERIES_2_CONFIG > 1)
CMU_ClockEnable(cmuClock_LDMAXBAR, true);
#endif
#if defined(LDMA_EN_EN)
LDMA->EN = LDMA_EN_EN;
#endif
ldmaCtrlVal = (uint32_t)init->ldmaInitCtrlNumFixed << _LDMA_CTRL_NUMFIXED_SHIFT;
#if defined(_LDMA_CTRL_SYNCPRSCLREN_SHIFT) && defined (_LDMA_CTRL_SYNCPRSSETEN_SHIFT)
ldmaCtrlVal |= (init->ldmaInitCtrlSyncPrsClrEn << _LDMA_CTRL_SYNCPRSCLREN_SHIFT)
| (init->ldmaInitCtrlSyncPrsSetEn << _LDMA_CTRL_SYNCPRSSETEN_SHIFT);
#endif
LDMA->CTRL = ldmaCtrlVal;
#if defined(_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
LDMA->SYNCHWEN = ((uint32_t)init->ldmaInitCtrlSyncPrsClrEn << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
| ((uint32_t)init->ldmaInitCtrlSyncPrsSetEn << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT);
#endif
#if defined(_LDMA_CHDIS_MASK)
LDMA->CHDIS = _LDMA_CHEN_MASK;
#else
LDMA->CHEN = 0;
#endif
LDMA->DBGHALT = 0;
LDMA->REQDIS = 0;
/* Enable the LDMA error interrupt. */
LDMA->IEN = LDMA_IEN_ERROR;
#if defined (LDMA_HAS_SET_CLEAR)
LDMA->IF_CLR = 0xFFFFFFFFU;
#else
LDMA->IFC = 0xFFFFFFFFU;
#endif
NVIC_ClearPendingIRQ(LDMA_IRQn);
/* Range is 0-7, where 0 is the highest priority. */
NVIC_SetPriority(LDMA_IRQn, init->ldmaInitIrqPriority);
NVIC_EnableIRQ(LDMA_IRQn);
}
/***************************************************************************//**
* @brief
* Start a DMA transfer.
*
* @param[in] ch
* A DMA channel.
*
* @param[in] transfer
* The initialization structure used to configure the transfer.
*
* @param[in] descriptor
* The transfer descriptor, which can be an array of descriptors linked together.
* Each descriptor's fields stored in RAM will be loaded into the certain
* hardware registers at the proper time to perform the DMA transfer.
******************************************************************************/
void LDMA_StartTransfer(int ch,
const LDMA_TransferCfg_t *transfer,
const LDMA_Descriptor_t *descriptor)
{
#if !(defined (_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT))
uint32_t tmp;
#endif
CORE_DECLARE_IRQ_STATE;
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
EFM_ASSERT(transfer != NULL);
#if defined (_LDMAXBAR_CH_REQSEL_MASK)
EFM_ASSERT(!(transfer->ldmaReqSel & ~_LDMAXBAR_CH_REQSEL_MASK));
#elif defined (_LDMA_CH_REQSEL_MASK)
EFM_ASSERT(!(transfer->ldmaReqSel & ~_LDMA_CH_REQSEL_MASK));
#endif
EFM_ASSERT(!(((uint32_t)transfer->ldmaCfgArbSlots << _LDMA_CH_CFG_ARBSLOTS_SHIFT)
& ~_LDMA_CH_CFG_ARBSLOTS_MASK));
EFM_ASSERT(!(((uint32_t)transfer->ldmaCfgSrcIncSign << _LDMA_CH_CFG_SRCINCSIGN_SHIFT)
& ~_LDMA_CH_CFG_SRCINCSIGN_MASK));
EFM_ASSERT(!(((uint32_t)transfer->ldmaCfgDstIncSign << _LDMA_CH_CFG_DSTINCSIGN_SHIFT)
& ~_LDMA_CH_CFG_DSTINCSIGN_MASK));
/* Clear the pending channel interrupt. */
#if defined (LDMA_HAS_SET_CLEAR)
LDMA->IF_CLR = chMask;
#else
LDMA->IFC = chMask;
#endif
#if defined(LDMAXBAR)
LDMAXBAR->CH[ch].REQSEL = transfer->ldmaReqSel;
#else
LDMA->CH[ch].REQSEL = transfer->ldmaReqSel;
#endif
LDMA->CH[ch].LOOP = transfer->ldmaLoopCnt << _LDMA_CH_LOOP_LOOPCNT_SHIFT;
LDMA->CH[ch].CFG = (transfer->ldmaCfgArbSlots << _LDMA_CH_CFG_ARBSLOTS_SHIFT)
| (transfer->ldmaCfgSrcIncSign << _LDMA_CH_CFG_SRCINCSIGN_SHIFT)
| (transfer->ldmaCfgDstIncSign << _LDMA_CH_CFG_DSTINCSIGN_SHIFT)
#if defined(_LDMA_CH_CFG_SRCBUSPORT_MASK)
| (transfer->ldmaCfgStructBusPort << _LDMA_CH_CFG_STRUCTBUSPORT_SHIFT)
| (transfer->ldmaCfgSrcBusPort << _LDMA_CH_CFG_SRCBUSPORT_SHIFT)
| (transfer->ldmaCfgDstBusPort << _LDMA_CH_CFG_DSTBUSPORT_SHIFT)
#endif
;
/* Set the descriptor address. */
LDMA->CH[ch].LINK = (uint32_t)descriptor & _LDMA_CH_LINK_LINKADDR_MASK;
/* A critical region. */
CORE_ENTER_ATOMIC();
/* Enable the channel interrupt. */
BUS_RegMaskedSet(&LDMA->IEN, chMask);
if (transfer->ldmaReqDis) {
LDMA->REQDIS |= chMask;
}
if (transfer->ldmaDbgHalt) {
LDMA->DBGHALT |= chMask;
}
#if defined (_LDMA_SYNCHWEN_SYNCCLREN_SHIFT) && defined (_LDMA_SYNCHWEN_SYNCSETEN_SHIFT)
LDMA->SYNCHWEN_CLR =
(((uint32_t)transfer->ldmaCtrlSyncPrsClrOff << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
| ((uint32_t)transfer->ldmaCtrlSyncPrsSetOff << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT))
& _LDMA_SYNCHWEN_MASK;
LDMA->SYNCHWEN_SET =
(((uint32_t)transfer->ldmaCtrlSyncPrsClrOn << _LDMA_SYNCHWEN_SYNCCLREN_SHIFT)
| ((uint32_t)transfer->ldmaCtrlSyncPrsSetOn << _LDMA_SYNCHWEN_SYNCSETEN_SHIFT))
& _LDMA_SYNCHWEN_MASK;
#elif defined (_LDMA_CTRL_SYNCPRSCLREN_SHIFT) && defined (_LDMA_CTRL_SYNCPRSSETEN_SHIFT)
tmp = LDMA->CTRL;
if (transfer->ldmaCtrlSyncPrsClrOff) {
tmp &= ~_LDMA_CTRL_SYNCPRSCLREN_MASK
| (~transfer->ldmaCtrlSyncPrsClrOff << _LDMA_CTRL_SYNCPRSCLREN_SHIFT);
}
if (transfer->ldmaCtrlSyncPrsClrOn) {
tmp |= transfer->ldmaCtrlSyncPrsClrOn << _LDMA_CTRL_SYNCPRSCLREN_SHIFT;
}
if (transfer->ldmaCtrlSyncPrsSetOff) {
tmp &= ~_LDMA_CTRL_SYNCPRSSETEN_MASK
| (~transfer->ldmaCtrlSyncPrsSetOff << _LDMA_CTRL_SYNCPRSSETEN_SHIFT);
}
if (transfer->ldmaCtrlSyncPrsSetOn) {
tmp |= transfer->ldmaCtrlSyncPrsSetOn << _LDMA_CTRL_SYNCPRSSETEN_SHIFT;
}
LDMA->CTRL = tmp;
#else
#error "SYNC Set and SYNC Clear not defined"
#endif
BUS_RegMaskedClear(&LDMA->CHDONE, chMask); /* Clear the done flag. */
LDMA->LINKLOAD = chMask; /* Start a transfer by loading the descriptor. */
/* A critical region end. */
CORE_EXIT_ATOMIC();
}
#if defined(_LDMA_CH_CTRL_EXTEND_MASK)
/***************************************************************************//**
* @brief
* Start an extended DMA transfer.
*
* @param[in] ch
* A DMA channel.
*
* @param[in] transfer
* The initialization structure used to configure the transfer.
*
* @param[in] descriptor_ext
* The extended transfer descriptor, which can be an array of descriptors
* linked together. Each descriptor's fields stored in RAM will be loaded
* into the certain hardware registers at the proper time to perform the DMA
* transfer.
******************************************************************************/
void LDMA_StartTransferExtend(int ch,
const LDMA_TransferCfg_t *transfer,
const LDMA_DescriptorExtend_t *descriptor_ext)
{
// Ensure destination interleaving supported for given channel.
EFM_ASSERT(((1 << ch) & LDMA_ILCHNL));
LDMA_StartTransfer(ch,
transfer,
(const LDMA_Descriptor_t *)descriptor_ext);
}
#endif
/***************************************************************************//**
* @brief
* Stop a DMA transfer.
*
* @note
* The DMA will complete the current AHB burst transfer before stopping.
*
* @param[in] ch
* A DMA channel to stop.
******************************************************************************/
void LDMA_StopTransfer(int ch)
{
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
#if defined(_LDMA_CHDIS_MASK)
CORE_ATOMIC_SECTION(
LDMA->IEN &= ~chMask;
LDMA->CHDIS = chMask;
)
#else
CORE_ATOMIC_SECTION(
LDMA->IEN &= ~chMask;
BUS_RegMaskedClear(&LDMA->CHEN, chMask);
)
#endif
}
/***************************************************************************//**
* @brief
* Check if a DMA transfer has completed.
*
* @param[in] ch
* A DMA channel to check.
*
* @return
* True if transfer has completed, false if not.
******************************************************************************/
bool LDMA_TransferDone(int ch)
{
bool retVal = false;
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
#if defined(_LDMA_CHSTATUS_MASK)
CORE_ATOMIC_SECTION(
if (((LDMA->CHSTATUS & chMask) == 0) && ((LDMA->CHDONE & chMask) == chMask)) {
retVal = true;
}
)
#else
CORE_ATOMIC_SECTION(
if (((LDMA->CHEN & chMask) == 0) && ((LDMA->CHDONE & chMask) == chMask)) {
retVal = true;
}
)
#endif
return retVal;
}
/***************************************************************************//**
* @brief
* Get the number of items remaining in a transfer.
*
* @note
* This function does not take into account that a DMA transfer with
* a chain of linked transfers might be ongoing. It will only check the
* count for the current transfer.
*
* @param[in] ch
* The channel number of the transfer to check.
*
* @return
* A number of items remaining in the transfer.
******************************************************************************/
uint32_t LDMA_TransferRemainingCount(int ch)
{
uint32_t remaining, done;
uint32_t chMask = 1UL << (uint8_t)ch;
EFM_ASSERT(ch < (int)DMA_CHAN_COUNT);
CORE_ATOMIC_SECTION(
done = LDMA->CHDONE;
remaining = LDMA->CH[ch].CTRL;
)
done &= chMask;
if (done) {
return 0;
}
remaining = (remaining & _LDMA_CH_CTRL_XFERCNT_MASK)
>> _LDMA_CH_CTRL_XFERCNT_SHIFT;
/* +1 because XFERCNT is 0-based. */
return remaining + 1;
}
/** @} (end addtogroup ldma) */
#endif /* defined( LDMA_PRESENT ) && ( LDMA_COUNT == 1 ) */

View File

@@ -0,0 +1,685 @@
/***************************************************************************//**
* @file
* @brief Low Energy Timer (LETIMER) Peripheral API
*******************************************************************************
* # 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_letimer.h"
#if defined(LETIMER_COUNT) && (LETIMER_COUNT > 0)
#include "em_cmu.h"
#include "sl_assert.h"
/***************************************************************************//**
* @addtogroup letimer LETIMER - Low Energy Timer
* @brief Low Energy Timer (LETIMER) Peripheral API
* @details
* This module contains functions to control the LETIMER peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The LETIMER is a down-counter that can keep track
* of time and output configurable waveforms.
* @{
******************************************************************************/
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/** A validation of the valid comparator register for assert statements. */
#define LETIMER_COMP_REG_VALID(reg) (((reg) <= 1))
/** A validation of the LETIMER register block pointer reference for assert statements. */
#if (LETIMER_COUNT == 1)
#define LETIMER_REF_VALID(ref) ((ref) == LETIMER0)
#elif (LETIMER_COUNT == 2)
#define LETIMER_REF_VALID(ref) (((ref) == LETIMER0) || ((ref) == LETIMER1))
#else
#error Undefined number of analog comparators (ACMP).
#endif
/** A validation of the valid repeat counter register for assert statements. */
#define LETIMER_REP_REG_VALID(reg) (((reg) <= 1))
/** @endcond */
/*******************************************************************************
************************** LOCAL FUNCTIONS ********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/***************************************************************************//**
* @brief
* Wait for an ongoing sync of register(s) to the low-frequency domain to complete.
*
* @note
* See the reference manual chapter about Access to Low Energy Peripherals
* (Asynchronos Registers) for details.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] mask
* A bitmask corresponding to SYNCBUSY register defined bits, indicating
* registers that must complete any ongoing synchronization.
******************************************************************************/
__STATIC_INLINE void regSync(LETIMER_TypeDef *letimer, uint32_t mask)
{
#if defined(_LETIMER_FREEZE_MASK)
/* Avoid a deadlock if modifying the same register twice when freeze mode is */
/* activated. */
if (letimer->FREEZE & LETIMER_FREEZE_REGFREEZE) {
return;
}
#endif
/* Wait for any pending write operation to complete. */
while (letimer->SYNCBUSY & mask) {
}
}
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Get the LETIMER compare register value.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] comp
* A compare register to get, either 0 or 1.
*
* @return
* A compare register value, 0 if invalid register selected.
******************************************************************************/
uint32_t LETIMER_CompareGet(LETIMER_TypeDef *letimer, unsigned int comp)
{
uint32_t ret;
EFM_ASSERT(LETIMER_REF_VALID(letimer) && LETIMER_COMP_REG_VALID(comp));
/* Initialize the selected compare value. */
switch (comp) {
case 0:
#if defined(LETIMER_SYNCBUSY_COMP0)
regSync(letimer, LETIMER_SYNCBUSY_COMP0);
#endif
ret = letimer->COMP0;
break;
case 1:
#if defined(LETIMER_SYNCBUSY_COMP1)
regSync(letimer, LETIMER_SYNCBUSY_COMP1);
#endif
ret = letimer->COMP1;
break;
default:
/* An unknown compare register selected. */
ret = 0;
break;
}
return ret;
}
/***************************************************************************//**
* @brief
* Get LETIMER counter value.
*
* @param[in] letimer
* Pointer to the LETIMER peripheral register block.
*
* @return
* Current LETIMER counter value.
******************************************************************************/
uint32_t LETIMER_CounterGet(LETIMER_TypeDef *letimer)
{
#if defined(LETIMER_SYNCBUSY_CNT)
regSync(letimer, LETIMER_SYNCBUSY_CNT);
#endif
return letimer->CNT;
}
#if !defined(_EFM32_GECKO_FAMILY)
/***************************************************************************//**
* @brief
* Set LETIMER counter value.
*
* @param[in] letimer
* Pointer to the LETIMER peripheral register block.
*
* @param[in] value
* New counter value.
******************************************************************************/
void LETIMER_CounterSet(LETIMER_TypeDef *letimer, uint32_t value)
{
#if defined(LETIMER_SYNCBUSY_CNT)
regSync(letimer, LETIMER_SYNCBUSY_CNT);
#endif
letimer->CNT = value;
}
#endif
/***************************************************************************//**
* @brief
* Set the LETIMER compare register value.
*
* @note
* The setting of a compare register 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. This only applies to the Gecko Family. See
* comments in the LETIMER_Sync() internal function call.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] comp
* A compare register to set, either 0 or 1.
*
* @param[in] value
* An initialization value (<= 0x0000ffff).
******************************************************************************/
void LETIMER_CompareSet(LETIMER_TypeDef *letimer,
unsigned int comp,
uint32_t value)
{
EFM_ASSERT(LETIMER_REF_VALID(letimer)
&& LETIMER_COMP_REG_VALID(comp)
&& ((value & ~(_LETIMER_COMP0_COMP0_MASK
>> _LETIMER_COMP0_COMP0_SHIFT))
== 0));
/* Initialize the selected compare value. */
switch (comp) {
case 0:
#if defined(LETIMER_SYNCBUSY_COMP0)
regSync(letimer, LETIMER_SYNCBUSY_COMP0);
#endif
letimer->COMP0 = value;
break;
case 1:
#if defined(LETIMER_SYNCBUSY_COMP1)
regSync(letimer, LETIMER_SYNCBUSY_COMP1);
#endif
letimer->COMP1 = value;
break;
default:
/* An unknown compare register selected, abort. */
break;
}
}
/***************************************************************************//**
* @brief
* Start/stop LETIMER.
*
* @note
* The enabling/disabling of the LETIMER modifies the LETIMER CMD register
* which requires synchronization into the low-frequency domain. If this
* register is modified before a previous update to the same register has
* completed, this function will stall until the previous synchronization has
* completed. This only applies to the Gecko Family. See comments in the
* LETIMER_Sync() internal function call.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] enable
* True to enable counting, false to disable.
******************************************************************************/
void LETIMER_Enable(LETIMER_TypeDef *letimer, bool enable)
{
EFM_ASSERT(LETIMER_REF_VALID(letimer));
#if defined(LETIMER_SYNCBUSY_CMD)
regSync(letimer, LETIMER_SYNCBUSY_CMD);
#elif defined (LETIMER_SYNCBUSY_START) && defined (LETIMER_SYNCBUSY_STOP)
regSync(letimer, LETIMER_SYNCBUSY_STOP | LETIMER_SYNCBUSY_START);
#endif
if (enable) {
letimer->CMD = LETIMER_CMD_START;
} else {
letimer->CMD = LETIMER_CMD_STOP;
}
}
#if defined(_LETIMER_FREEZE_MASK)
/***************************************************************************//**
* @brief
* LETIMER register synchronization freeze control.
*
* @details
* Some LETIMER 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 LETIMER synchronization to the LF domain to complete (Normally
* synchronization will not be in progress.) However, for this reason, when
* using freeze mode, modifications of registers requiring the LF synchronization
* should be done within one freeze enable/disable block to avoid unecessary
* stalling.
*
* @param[in] letimer
* A pointer to the LETIMER 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 the LF
* domain
******************************************************************************/
void LETIMER_FreezeEnable(LETIMER_TypeDef *letimer, bool enable)
{
if (enable) {
/*
* Wait for any ongoing LF synchronization to complete 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 (letimer->SYNCBUSY) {
}
letimer->FREEZE = LETIMER_FREEZE_REGFREEZE;
} else {
letimer->FREEZE = 0;
}
}
#endif /* defined(_LETIMER_FREEZE_MASK) */
/***************************************************************************//**
* @brief
* Initialize LETIMER.
*
* @details
* Note that the compare/repeat values must be set separately with
* LETIMER_CompareSet() and LETIMER_RepeatSet(). That should probably be done
* prior using this function if configuring the LETIMER to start when
* initialization is complete.
*
* @note
* The initialization of the LETIMER modifies the LETIMER CTRL/CMD registers
* which require synchronization into the low-frequency domain. If any of those
* registers are modified before a previous update to the same register has
* completed, this function will stall until the previous synchronization has
* completed. This only applies to the Gecko Family. See comments in the
* LETIMER_Sync() internal function call.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] init
* A pointer to the LETIMER initialization structure.
******************************************************************************/
void LETIMER_Init(LETIMER_TypeDef *letimer, const LETIMER_Init_TypeDef *init)
{
uint32_t tmp = 0;
EFM_ASSERT(LETIMER_REF_VALID(letimer));
#if defined (LETIMER_EN_EN)
letimer->EN_SET = LETIMER_EN_EN;
#endif
/* Stop the timer if specified to be disabled and running. */
if (!(init->enable) && (letimer->STATUS & LETIMER_STATUS_RUNNING)) {
#if defined(LETIMER_SYNCBUSY_CMD)
regSync(letimer, LETIMER_SYNCBUSY_CMD);
#elif defined(LETIMER_SYNCBUSY_STOP)
regSync(letimer, LETIMER_SYNCBUSY_STOP);
#endif
letimer->CMD = LETIMER_CMD_STOP;
}
/* Configure the DEBUGRUN flag, which sets whether or not the counter should be
* updated when the debugger is active. */
if (init->debugRun) {
tmp |= LETIMER_CTRL_DEBUGRUN;
}
#if defined(LETIMER_CTRL_RTCC0TEN)
if (init->rtcComp0Enable) {
tmp |= LETIMER_CTRL_RTCC0TEN;
}
if (init->rtcComp1Enable) {
tmp |= LETIMER_CTRL_RTCC1TEN;
}
#endif
if ((init->comp0Top) || (init->topValue != 0U)) {
#if defined (LETIMER_CTRL_COMP0TOP)
tmp |= LETIMER_CTRL_COMP0TOP;
if (init->topValue != 0U) {
letimer->COMP0 = init->topValue;
}
#elif defined (LETIMER_CTRL_CNTTOPEN)
tmp |= LETIMER_CTRL_CNTTOPEN;
if (init->topValue != 0U) {
letimer->TOP = init->topValue;
}
#endif
}
if (init->bufTop) {
tmp |= LETIMER_CTRL_BUFTOP;
}
if (init->out0Pol) {
tmp |= LETIMER_CTRL_OPOL0;
}
if (init->out1Pol) {
tmp |= LETIMER_CTRL_OPOL1;
}
tmp |= init->ufoa0 << _LETIMER_CTRL_UFOA0_SHIFT;
tmp |= init->ufoa1 << _LETIMER_CTRL_UFOA1_SHIFT;
tmp |= init->repMode << _LETIMER_CTRL_REPMODE_SHIFT;
#if defined(LETIMER_SYNCBUSY_CTRL)
/* LF register about to be modified requires sync; busy check. */
regSync(letimer, LETIMER_SYNCBUSY_CTRL);
#endif
letimer->CTRL = tmp;
/* Start the timer if specified to be enabled and not already running. */
if (init->enable && !(letimer->STATUS & LETIMER_STATUS_RUNNING)) {
#if defined(LETIMER_SYNCBUSY_CMD)
regSync(letimer, LETIMER_SYNCBUSY_CMD);
#elif defined(LETIMER_SYNCBUSY_START)
regSync(letimer, LETIMER_SYNCBUSY_START);
#endif
letimer->CMD = LETIMER_CMD_START;
}
}
/***************************************************************************//**
* @brief
* Get the LETIMER repeat register value.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] rep
* Repeat register to get, either 0 or 1.
*
* @return
* Repeat register value, 0 if invalid register selected.
******************************************************************************/
uint32_t LETIMER_RepeatGet(LETIMER_TypeDef *letimer, unsigned int rep)
{
uint32_t ret;
EFM_ASSERT(LETIMER_REF_VALID(letimer) && LETIMER_REP_REG_VALID(rep));
/* Initialize the selected compare value. */
switch (rep) {
case 0:
#if defined(LETIMER_SYNCBUSY_REP0)
/* Wait for sync to complete to read the potentially pending value. */
regSync(letimer, LETIMER_SYNCBUSY_REP0);
#endif
ret = letimer->REP0;
break;
case 1:
#if defined(LETIMER_SYNCBUSY_REP1)
regSync(letimer, LETIMER_SYNCBUSY_REP1);
#endif
ret = letimer->REP1;
break;
default:
/* An unknown compare register selected. */
ret = 0;
break;
}
return ret;
}
/***************************************************************************//**
* @brief
* Set the LETIMER repeat counter register value.
*
* @note
* The setting of a repeat counter register 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. This only applies to the Gecko Family. See
* comments in the LETIMER_Sync() internal function call.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] rep
* Repeat counter register to set, either 0 or 1.
*
* @param[in] value
* An initialization value (<= 0x0000ffff).
******************************************************************************/
void LETIMER_RepeatSet(LETIMER_TypeDef *letimer,
unsigned int rep,
uint32_t value)
{
EFM_ASSERT(LETIMER_REF_VALID(letimer)
&& LETIMER_REP_REG_VALID(rep)
&& ((value & ~(_LETIMER_REP0_REP0_MASK
>> _LETIMER_REP0_REP0_SHIFT))
== 0));
/* Initialize the selected compare value. */
switch (rep) {
case 0:
#if defined(LETIMER_SYNCBUSY_REP0)
regSync(letimer, LETIMER_SYNCBUSY_REP0);
#endif
letimer->REP0 = value;
break;
case 1:
#if defined(LETIMER_SYNCBUSY_REP1)
regSync(letimer, LETIMER_SYNCBUSY_REP1);
#endif
letimer->REP1 = value;
break;
default:
/* An unknown compare register selected, abort. */
break;
}
}
/***************************************************************************//**
* @brief
* Reset LETIMER 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] letimer
* A pointer to the LETIMER peripheral register block.
******************************************************************************/
void LETIMER_Reset(LETIMER_TypeDef *letimer)
{
#if defined(LETIMER_EN_EN)
letimer->EN_SET = LETIMER_EN_EN;
#endif
LETIMER_SyncWait(letimer);
#if defined(LETIMER_SWRST_SWRST)
letimer->SWRST_SET = LETIMER_SWRST_SWRST;
while (letimer->SWRST & _LETIMER_SWRST_RESETTING_MASK) {
}
#else
#if defined(_LETIMER_FREEZE_MASK)
/* Freeze registers to avoid stalling for LF synchronization. */
LETIMER_FreezeEnable(letimer, true);
#endif
/* Make sure disabled first, before resetting other registers. */
letimer->CMD = LETIMER_CMD_STOP | LETIMER_CMD_CLEAR
| LETIMER_CMD_CTO0 | LETIMER_CMD_CTO1;
letimer->CTRL = _LETIMER_CTRL_RESETVALUE;
letimer->COMP0 = _LETIMER_COMP0_RESETVALUE;
letimer->COMP1 = _LETIMER_COMP1_RESETVALUE;
letimer->REP0 = _LETIMER_REP0_RESETVALUE;
letimer->REP1 = _LETIMER_REP1_RESETVALUE;
letimer->IEN = _LETIMER_IEN_RESETVALUE;
LETIMER_IntClear(letimer, _LETIMER_IF_MASK);
#if defined(_LETIMER_FREEZE_MASK)
/* Unfreeze registers and pass new settings to LETIMER. */
LETIMER_FreezeEnable(letimer, false);
#endif
LETIMER_SyncWait(letimer);
#if defined (LETIMER_EN_EN)
letimer->EN_CLR = LETIMER_EN_EN;
#if defined(_LETIMER_EN_DISABLING_MASK)
/*
* Currently, there are no chips without SWRST and with LETIMER_EN_DISABLING
* so this code should never be reached, but that way the same pattern of
* checking the disabling bit is spread across emlib, and code is slightly
* more resilient to feature addition/removal.
*/
while (letimer->EN & _LETIMER_EN_DISABLING_MASK) {
}
#endif
#endif
#endif
}
/***************************************************************************//**
* @brief
* Wait for the LETIMER to complete all synchronization of register changes
* and commands.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
******************************************************************************/
void LETIMER_SyncWait(LETIMER_TypeDef *letimer)
{
#if defined(_SILICON_LABS_32B_SERIES_2)
while ((letimer->EN != 0U) && (letimer->SYNCBUSY != 0U)) {
/* Wait for previous synchronization to finish */
}
#else
while (letimer->SYNCBUSY != 0U) {
/* Wait for previous synchronization to finish */
}
#endif
}
/***************************************************************************//**
* @brief
* Set the LETIMER top value.
*
* @note
* The LETIMER is a down-counter, so when the counter reaches 0 then the top
* value will be loaded into the counter. This function can be used to set
* the top value.
*
* If the LETIMER is not already configured to use a top value then this
* function will enable that functionality for the user.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @param[in] value
* The top value. This can be a 16 bit value on series-0 and series-1 devices
* and a 24 bit value on series-2 devices.
******************************************************************************/
void LETIMER_TopSet(LETIMER_TypeDef *letimer, uint32_t value)
{
#if defined(LETIMER_SYNCBUSY_CTRL)
regSync(letimer, LETIMER_SYNCBUSY_CTRL);
#elif defined(LETIMER_SYNCBUSY_TOP)
regSync(letimer, LETIMER_SYNCBUSY_TOP);
#endif
#if defined(_LETIMER_TOP_MASK)
/* Make sure TOP value is enabled. */
if ((letimer->CTRL & LETIMER_CTRL_CNTTOPEN) == 0U) {
letimer->CTRL_SET = LETIMER_CTRL_CNTTOPEN;
}
letimer->TOP = value;
#else
/* Make sure TOP value is enabled. */
if ((letimer->CTRL & LETIMER_CTRL_COMP0TOP) == 0U) {
letimer->CTRL |= LETIMER_CTRL_COMP0TOP;
}
LETIMER_CompareSet(letimer, 0, value);
#endif
}
/***************************************************************************//**
* @brief
* Get the current LETIMER top value.
*
* @param[in] letimer
* A pointer to the LETIMER peripheral register block.
*
* @return
* The top value. This will be a 16 bit value on series-0 and series-1
* devices and a 24 bit value on series-2 devices.
******************************************************************************/
uint32_t LETIMER_TopGet(LETIMER_TypeDef *letimer)
{
#if defined(_LETIMER_TOP_MASK)
regSync(letimer, LETIMER_SYNCBUSY_TOP);
return letimer->TOP;
#else
#if defined(LETIMER_SYNCBUSY_COMP0)
regSync(letimer, LETIMER_SYNCBUSY_COMP0);
#endif
return letimer->COMP0;
#endif
}
/** @} (end addtogroup letimer) */
#endif /* defined(LETIMER_COUNT) && (LETIMER_COUNT > 0) */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,700 @@
/***************************************************************************//**
* @file
* @brief Operational Amplifier (OPAMP) peripheral API
*******************************************************************************
* # 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_opamp.h"
#if ((defined(_SILICON_LABS_32B_SERIES_0) && defined(OPAMP_PRESENT) && (OPAMP_COUNT == 1)) \
|| (defined(_SILICON_LABS_32B_SERIES_1) && defined(VDAC_PRESENT) && (VDAC_COUNT > 0)))
#include "em_system.h"
#include "sl_assert.h"
/* *INDENT-OFF* */
/***************************************************************************//**
* @addtogroup opamp OPAMP - Operational Amplifier
* @brief Operational Amplifier (OPAMP) peripheral API
* @details
* This module contains functions to:
* @li OPAMP_Enable() Configure and enable OPAMP.
* @li OPAMP_Disable() Disable OPAMP.
*
* @if DOXYDOC_P1_DEVICE
* All OPAMP functions assume that the DAC clock is running. If DAC is not
* used, the clock can be turned off when OPAMPs are configured.
* @elseif DOXYDOC_P2_DEVICE
* All OPAMP functions assume that the VDAC clock is running. If VDAC is not
* used, the clock can be turned off when the OPAMPs are configured.
* @endif
*
* If the available gain values don't suit the application at hand, the resistor
* ladders can be disabled and external gain programming resistors used.
*
* A number of predefined OPAMP setup macros are available for configuration
* of the most common OPAMP topologies (see figures below).
*
* @note
* <em>The terms POSPAD and NEGPAD in the figures are used to indicate that these
* pads should be connected to a suitable signal ground.</em>
*
* \n<b>Unity gain voltage follower.</b>\n
* @if DOXYDOC_P1_DEVICE
* Use predefined macros @ref OPA_INIT_UNITY_GAIN and
* @ref OPA_INIT_UNITY_GAIN_OPA2.
* @elseif DOXYDOC_P2_DEVICE
* Use predefined macro @ref OPA_INIT_UNITY_GAIN.
* @endif
* @verbatim
|\
___________|+\
| \_______
___|_ / |
| | / |
| |/ |
|___________|
@endverbatim
*
* \n<b>Non-inverting amplifier.</b>\n
* @if DOXYDOC_P1_DEVICE
* Use predefined macros @ref OPA_INIT_NON_INVERTING and
* @ref OPA_INIT_NON_INVERTING_OPA2.
* @elseif DOXYDOC_P2_DEVICE
* Use predefined macro @ref OPA_INIT_NON_INVERTING.
* @endif
* @verbatim
|\
___________|+\
| \_______
___|_ / |
| | / |
| |/ |
|_____R2____|
|
R1
|
NEGPAD @endverbatim
*
* \n<b>Inverting amplifier.</b>\n
* @if DOXYDOC_P1_DEVICE
* Use predefined macros @ref OPA_INIT_INVERTING and
* @ref OPA_INIT_INVERTING_OPA2.
* @elseif DOXYDOC_P2_DEVICE
* Use predefined macro @ref OPA_INIT_INVERTING.
* @endif
* @verbatim
_____R2____
| |
| |\ |
____R1_|___|_\ |
| \____|___
___| /
| |+/
| |/
|
POSPAD @endverbatim
*
* \n<b>Cascaded non-inverting amplifiers.</b>\n
* Use predefined macros @ref OPA_INIT_CASCADED_NON_INVERTING_OPA0,
* @ref OPA_INIT_CASCADED_NON_INVERTING_OPA1 and
* @ref OPA_INIT_CASCADED_NON_INVERTING_OPA2.
* @verbatim
|\ |\ |\
___________|+\ OPA0 ___________|+\ OPA1 ___________|+\ OPA2
| \_________| | \_________| | \_______
___|_ / | ___|_ / | ___|_ / |
| | / | | | / | | | / |
| |/ | | |/ | | |/ |
|_____R2____| |_____R2____| |_____R2____|
| | |
R1 R1 R1
| | |
NEGPAD NEGPAD NEGPAD @endverbatim
*
* \n<b>Cascaded inverting amplifiers.</b>\n
* Use predefined macros @ref OPA_INIT_CASCADED_INVERTING_OPA0,
* @ref OPA_INIT_CASCADED_INVERTING_OPA1 and
* @ref OPA_INIT_CASCADED_INVERTING_OPA2.
* @verbatim
_____R2____ _____R2____ _____R2____
| | | | | |
| |\ | | |\ | | |\ |
____R1_|___|_\ | ____R1_|___|_\ | ____R1_|___|_\ |
| \____|____| | \____|___| | \____|__
___| / ___| / ___| /
| |+/ OPA0 | |+/ OPA1 | |+/ OPA2
| |/ | |/ | |/
| | |
POSPAD POSPAD POSPAD @endverbatim
*
* \n<b>Differential driver with two opamp's.</b>\n
* Use predefined macros @ref OPA_INIT_DIFF_DRIVER_OPA0 and
* @ref OPA_INIT_DIFF_DRIVER_OPA1.
* @verbatim
__________________________
| +
| _____R2____
|\ | | |
___________|+\ OPA0 | | |\ OPA1 |
| \_________|____R1_|___|_\ | _
___|_ / | | \____|______
| | / | ___| /
| |/ | | |+/
|________________| | |/
|
POSPAD @endverbatim
*
* \n<b>Differential receiver with three opamp's.</b>\n
* Use predefined macros @ref OPA_INIT_DIFF_RECEIVER_OPA0,
* @ref OPA_INIT_DIFF_RECEIVER_OPA1 and @ref OPA_INIT_DIFF_RECEIVER_OPA2.
* @verbatim
|\
__________|+\ OPA1
_ | \_________
___|_ / | | _____R2____
| | / | | | |
| |/ | | | |\ |
|___________| |____R1_|___|_\ |
| \____|___
|\ ____R1_ ___| /
+__________|+\ OPA0 | | |+/ OPA2
| \_________| | |/
___|_ / | R2
| | / | |
| |/ | NEGPAD OPA0
|___________|
@endverbatim
*
* @if DOXYDOC_P2_DEVICE
* \n<b>Instrumentation amplifier.</b>\n
* Use predefined macros @ref OPA_INIT_INSTR_AMP_OPA0 and
* @ref OPA_INIT_INSTR_AMP_OPA1.
* @verbatim
|\
__________|+\ OPA1
| \______________
___|_ / |
| | / |
| |/ R2
|____________|
|
R1
|
R1
____________|
| |
| R2
| |\ |
|___|+\ OPA0 |
| \_____|________
__________|_ /
| /
|/
@endverbatim
* @endif
*
* @{
******************************************************************************/
/* *INDENT-ON* */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Disable an Operational Amplifier.
*
* @if DOXYDOC_P1_DEVICE
* @param[in] dac
* A pointer to the DAC peripheral register block.
* @elseif DOXYDOC_P2_DEVICE
* @param[in] dac
* A pointer to the VDAC peripheral register block.
* @endif
*
* @param[in] opa
* Selects an OPA, valid values are OPA0, OPA1, and OPA2.
******************************************************************************/
void OPAMP_Disable(
#if defined(_SILICON_LABS_32B_SERIES_0)
DAC_TypeDef *dac,
#elif defined(_SILICON_LABS_32B_SERIES_1)
VDAC_TypeDef *dac,
#endif
OPAMP_TypeDef opa)
{
#if defined(_SILICON_LABS_32B_SERIES_0)
EFM_ASSERT(DAC_REF_VALID(dac));
EFM_ASSERT(DAC_OPA_VALID(opa));
if (opa == OPA0) {
dac->CH0CTRL &= ~DAC_CH0CTRL_EN;
dac->OPACTRL &= ~DAC_OPACTRL_OPA0EN;
} else if (opa == OPA1) {
dac->CH1CTRL &= ~DAC_CH1CTRL_EN;
dac->OPACTRL &= ~DAC_OPACTRL_OPA1EN;
} else { /* OPA2 */
dac->OPACTRL &= ~DAC_OPACTRL_OPA2EN;
}
#elif defined(_SILICON_LABS_32B_SERIES_1)
EFM_ASSERT(VDAC_REF_VALID(dac));
EFM_ASSERT(VDAC_OPA_VALID(opa));
if (opa == OPA0) {
#if defined(VDAC_STATUS_OPA0ENS)
dac->CMD |= VDAC_CMD_OPA0DIS;
while (dac->STATUS & VDAC_STATUS_OPA0ENS) {
}
#endif
#if defined(VDAC_STATUS_OPA1ENS)
} else if (opa == OPA1) {
dac->CMD |= VDAC_CMD_OPA1DIS;
while (dac->STATUS & VDAC_STATUS_OPA1ENS) {
}
#endif
#if defined(VDAC_STATUS_OPA2ENS)
} else if (opa == OPA2) {
dac->CMD |= VDAC_CMD_OPA2DIS;
while (dac->STATUS & VDAC_STATUS_OPA2ENS) {
}
#endif
} else { /* OPA3 */
#if defined(VDAC_STATUS_OPA3ENS)
dac->CMD |= VDAC_CMD_OPA3DIS;
while (dac->STATUS & VDAC_STATUS_OPA3ENS) {
}
#endif
}
#endif
}
/***************************************************************************//**
* @brief
* Configure and enable an Operational Amplifier.
*
* @if DOXYDOC_P1_DEVICE
* @note
* The value of the alternate output enable bit mask in the OPAMP_Init_TypeDef
* structure should consist of one or more of the
* DAC_OPA[opa#]MUX_OUTPEN_OUT[output#] flags
* (defined in \<part_name\>_dac.h) OR'ed together. @n @n
* For OPA0:
* @li DAC_OPA0MUX_OUTPEN_OUT0
* @li DAC_OPA0MUX_OUTPEN_OUT1
* @li DAC_OPA0MUX_OUTPEN_OUT2
* @li DAC_OPA0MUX_OUTPEN_OUT3
* @li DAC_OPA0MUX_OUTPEN_OUT4
*
* For OPA1:
* @li DAC_OPA1MUX_OUTPEN_OUT0
* @li DAC_OPA1MUX_OUTPEN_OUT1
* @li DAC_OPA1MUX_OUTPEN_OUT2
* @li DAC_OPA1MUX_OUTPEN_OUT3
* @li DAC_OPA1MUX_OUTPEN_OUT4
*
* For OPA2:
* @li DAC_OPA2MUX_OUTPEN_OUT0
* @li DAC_OPA2MUX_OUTPEN_OUT1
*
* E.g: @n
* init.outPen = DAC_OPA0MUX_OUTPEN_OUT0 | DAC_OPA0MUX_OUTPEN_OUT4;
*
* @param[in] dac
* A pointer to the DAC peripheral register block.
* @elseif DOXYDOC_P2_DEVICE
* @note
* The value of the alternate output enable bit mask in the OPAMP_Init_TypeDef
* structure should consist of one or more of the
* VDAC_OPA_OUT_ALTOUTPADEN_OUT[output#] flags
* (defined in \<part_name\>_vdac.h) OR'ed together. @n @n
* @li VDAC_OPA_OUT_ALTOUTPADEN_OUT0
* @li VDAC_OPA_OUT_ALTOUTPADEN_OUT1
* @li VDAC_OPA_OUT_ALTOUTPADEN_OUT2
* @li VDAC_OPA_OUT_ALTOUTPADEN_OUT3
* @li VDAC_OPA_OUT_ALTOUTPADEN_OUT4
*
* For example: @n
* init.outPen = VDAC_OPA_OUT_ALTOUTPADEN_OUT0 | VDAC_OPA_OUT_ALTOUTPADEN_OUT4;
* @param[in] dac
* A pointer to the VDAC peripheral register block.
* @endif
*
* @param[in] opa
* Selects an OPA, valid values are OPA0, OPA1, and OPA2.
*
* @param[in] init
* A pointer to a structure containing OPAMP initialization information.
******************************************************************************/
void OPAMP_Enable(
#if defined(_SILICON_LABS_32B_SERIES_0)
DAC_TypeDef *dac,
#elif defined(_SILICON_LABS_32B_SERIES_1)
VDAC_TypeDef *dac,
#endif
OPAMP_TypeDef opa,
const OPAMP_Init_TypeDef *init)
{
#if defined(_SILICON_LABS_32B_SERIES_0)
uint32_t gain;
EFM_ASSERT(DAC_REF_VALID(dac));
EFM_ASSERT(DAC_OPA_VALID(opa));
EFM_ASSERT(init->bias <= (_DAC_BIASPROG_BIASPROG_MASK
>> _DAC_BIASPROG_BIASPROG_SHIFT));
if (opa == OPA0) {
EFM_ASSERT((init->outPen & ~_DAC_OPA0MUX_OUTPEN_MASK) == 0);
dac->BIASPROG = (dac->BIASPROG
& ~(_DAC_BIASPROG_BIASPROG_MASK
| DAC_BIASPROG_HALFBIAS))
| (init->bias << _DAC_BIASPROG_BIASPROG_SHIFT)
| (init->halfBias ? DAC_BIASPROG_HALFBIAS : 0);
if (init->defaultOffset) {
gain = dac->CAL & _DAC_CAL_GAIN_MASK;
SYSTEM_GetCalibrationValue(&dac->CAL);
dac->CAL = (dac->CAL & ~_DAC_CAL_GAIN_MASK) | gain;
} else {
EFM_ASSERT(init->offset <= (_DAC_CAL_CH0OFFSET_MASK
>> _DAC_CAL_CH0OFFSET_SHIFT));
dac->CAL = (dac->CAL & ~_DAC_CAL_CH0OFFSET_MASK)
| (init->offset << _DAC_CAL_CH0OFFSET_SHIFT);
}
dac->OPA0MUX = (uint32_t)init->resSel
| (uint32_t)init->outMode
| init->outPen
| (uint32_t)init->resInMux
| (uint32_t)init->negSel
| (uint32_t)init->posSel
| (init->nextOut ? DAC_OPA0MUX_NEXTOUT : 0)
| (init->npEn ? DAC_OPA0MUX_NPEN : 0)
| (init->ppEn ? DAC_OPA0MUX_PPEN : 0);
dac->CH0CTRL |= DAC_CH0CTRL_EN;
dac->OPACTRL = (dac->OPACTRL
& ~(DAC_OPACTRL_OPA0SHORT
| _DAC_OPACTRL_OPA0LPFDIS_MASK
| DAC_OPACTRL_OPA0HCMDIS))
| (init->shortInputs ? DAC_OPACTRL_OPA0SHORT : 0)
| (init->lpfPosPadDisable
? DAC_OPACTRL_OPA0LPFDIS_PLPFDIS : 0)
| (init->lpfNegPadDisable
? DAC_OPACTRL_OPA0LPFDIS_NLPFDIS : 0)
| (init->hcmDisable ? DAC_OPACTRL_OPA0HCMDIS : 0)
| DAC_OPACTRL_OPA0EN;
} else if ( opa == OPA1 ) {
EFM_ASSERT((init->outPen & ~_DAC_OPA1MUX_OUTPEN_MASK) == 0);
dac->BIASPROG = (dac->BIASPROG
& ~(_DAC_BIASPROG_BIASPROG_MASK
| DAC_BIASPROG_HALFBIAS))
| (init->bias << _DAC_BIASPROG_BIASPROG_SHIFT)
| (init->halfBias ? DAC_BIASPROG_HALFBIAS : 0);
if (init->defaultOffset) {
gain = dac->CAL & _DAC_CAL_GAIN_MASK;
SYSTEM_GetCalibrationValue(&dac->CAL);
dac->CAL = (dac->CAL & ~_DAC_CAL_GAIN_MASK) | gain;
} else {
EFM_ASSERT(init->offset <= (_DAC_CAL_CH1OFFSET_MASK
>> _DAC_CAL_CH1OFFSET_SHIFT));
dac->CAL = (dac->CAL & ~_DAC_CAL_CH1OFFSET_MASK)
| (init->offset << _DAC_CAL_CH1OFFSET_SHIFT);
}
dac->OPA1MUX = (uint32_t)init->resSel
| (uint32_t)init->outMode
| init->outPen
| (uint32_t)init->resInMux
| (uint32_t)init->negSel
| (uint32_t)init->posSel
| (init->nextOut ? DAC_OPA1MUX_NEXTOUT : 0)
| (init->npEn ? DAC_OPA1MUX_NPEN : 0)
| (init->ppEn ? DAC_OPA1MUX_PPEN : 0);
dac->CH1CTRL |= DAC_CH1CTRL_EN;
dac->OPACTRL = (dac->OPACTRL
& ~(DAC_OPACTRL_OPA1SHORT
| _DAC_OPACTRL_OPA1LPFDIS_MASK
| DAC_OPACTRL_OPA1HCMDIS))
| (init->shortInputs ? DAC_OPACTRL_OPA1SHORT : 0)
| (init->lpfPosPadDisable
? DAC_OPACTRL_OPA1LPFDIS_PLPFDIS : 0)
| (init->lpfNegPadDisable
? DAC_OPACTRL_OPA1LPFDIS_NLPFDIS : 0)
| (init->hcmDisable ? DAC_OPACTRL_OPA1HCMDIS : 0)
| DAC_OPACTRL_OPA1EN;
} else { /* OPA2 */
EFM_ASSERT((init->posSel == DAC_OPA2MUX_POSSEL_DISABLE)
|| (init->posSel == DAC_OPA2MUX_POSSEL_POSPAD)
|| (init->posSel == DAC_OPA2MUX_POSSEL_OPA1INP)
|| (init->posSel == DAC_OPA2MUX_POSSEL_OPATAP));
EFM_ASSERT((init->outMode & ~DAC_OPA2MUX_OUTMODE) == 0);
EFM_ASSERT((init->outPen & ~_DAC_OPA2MUX_OUTPEN_MASK) == 0);
dac->BIASPROG = (dac->BIASPROG
& ~(_DAC_BIASPROG_OPA2BIASPROG_MASK
| DAC_BIASPROG_OPA2HALFBIAS))
| (init->bias << _DAC_BIASPROG_OPA2BIASPROG_SHIFT)
| (init->halfBias ? DAC_BIASPROG_OPA2HALFBIAS : 0);
if (init->defaultOffset) {
SYSTEM_GetCalibrationValue(&dac->OPAOFFSET);
} else {
EFM_ASSERT(init->offset <= (_DAC_OPAOFFSET_OPA2OFFSET_MASK
>> _DAC_OPAOFFSET_OPA2OFFSET_SHIFT));
dac->OPAOFFSET = (dac->OPAOFFSET & ~_DAC_OPAOFFSET_OPA2OFFSET_MASK)
| (init->offset << _DAC_OPAOFFSET_OPA2OFFSET_SHIFT);
}
dac->OPA2MUX = (uint32_t)init->resSel
| (uint32_t)init->outMode
| init->outPen
| (uint32_t)init->resInMux
| (uint32_t)init->negSel
| (uint32_t)init->posSel
| (init->nextOut ? DAC_OPA2MUX_NEXTOUT : 0)
| (init->npEn ? DAC_OPA2MUX_NPEN : 0)
| (init->ppEn ? DAC_OPA2MUX_PPEN : 0);
dac->OPACTRL = (dac->OPACTRL
& ~(DAC_OPACTRL_OPA2SHORT
| _DAC_OPACTRL_OPA2LPFDIS_MASK
| DAC_OPACTRL_OPA2HCMDIS))
| (init->shortInputs ? DAC_OPACTRL_OPA2SHORT : 0)
| (init->lpfPosPadDisable
? DAC_OPACTRL_OPA2LPFDIS_PLPFDIS : 0)
| (init->lpfNegPadDisable
? DAC_OPACTRL_OPA2LPFDIS_NLPFDIS : 0)
| (init->hcmDisable ? DAC_OPACTRL_OPA2HCMDIS : 0)
| DAC_OPACTRL_OPA2EN;
}
#elif defined(_SILICON_LABS_32B_SERIES_1)
uint32_t calData = 0;
uint32_t warmupTime;
EFM_ASSERT(VDAC_REF_VALID(dac));
EFM_ASSERT(VDAC_OPA_VALID(opa));
EFM_ASSERT(init->settleTime <= (_VDAC_OPA_TIMER_SETTLETIME_MASK
>> _VDAC_OPA_TIMER_SETTLETIME_SHIFT));
EFM_ASSERT(init->startupDly <= (_VDAC_OPA_TIMER_STARTUPDLY_MASK
>> _VDAC_OPA_TIMER_STARTUPDLY_SHIFT));
EFM_ASSERT((init->outPen & ~_VDAC_OPA_OUT_ALTOUTPADEN_MASK) == 0);
EFM_ASSERT((init->drvStr == opaDrvStrLowerAccLowStr)
|| (init->drvStr == opaDrvStrLowAccLowStr)
|| (init->drvStr == opaDrvStrHighAccHighStr)
|| (init->drvStr == opaDrvStrHigherAccHighStr));
/* Disable OPAMP before writing to registers. */
OPAMP_Disable(dac, opa);
/* Get the calibration value based on OPAMP, Drive Strength, and INCBW. */
switch (opa) {
#if defined(VDAC_STATUS_OPA0ENS)
case OPA0:
switch (init->drvStr) {
case opaDrvStrLowerAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA0CAL0 : DEVINFO->OPA0CAL4);
break;
case opaDrvStrLowAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA0CAL1 : DEVINFO->OPA0CAL5);
break;
case opaDrvStrHighAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA0CAL2 : DEVINFO->OPA0CAL6);
break;
case opaDrvStrHigherAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA0CAL3 : DEVINFO->OPA0CAL7);
break;
}
break;
#endif
#if defined(VDAC_STATUS_OPA1ENS)
case OPA1:
switch (init->drvStr) {
case opaDrvStrLowerAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA1CAL0 : DEVINFO->OPA1CAL4);
break;
case opaDrvStrLowAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA1CAL1 : DEVINFO->OPA1CAL5);
break;
case opaDrvStrHighAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA1CAL2 : DEVINFO->OPA1CAL6);
break;
case opaDrvStrHigherAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA1CAL3 : DEVINFO->OPA1CAL7);
break;
}
break;
#endif
#if defined(VDAC_STATUS_OPA2ENS)
case OPA2:
switch (init->drvStr) {
case opaDrvStrLowerAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA2CAL0 : DEVINFO->OPA2CAL4);
break;
case opaDrvStrLowAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA2CAL1 : DEVINFO->OPA2CAL5);
break;
case opaDrvStrHighAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA2CAL2 : DEVINFO->OPA2CAL6);
break;
case opaDrvStrHigherAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA2CAL3 : DEVINFO->OPA2CAL7);
break;
}
break;
#endif
#if defined(VDAC_STATUS_OPA3ENS)
case OPA3:
switch (init->drvStr) {
case opaDrvStrLowerAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA3CAL0 : DEVINFO->OPA3CAL4);
break;
case opaDrvStrLowAccLowStr:
calData = (init->ugBwScale ? DEVINFO->OPA3CAL1 : DEVINFO->OPA3CAL5);
break;
case opaDrvStrHighAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA3CAL2 : DEVINFO->OPA3CAL6);
break;
case opaDrvStrHigherAccHighStr:
calData = (init->ugBwScale ? DEVINFO->OPA3CAL3 : DEVINFO->OPA3CAL7);
break;
}
break;
#endif
}
if (!init->defaultOffsetN) {
EFM_ASSERT(init->offsetN <= (_VDAC_OPA_CAL_OFFSETN_MASK
>> _VDAC_OPA_CAL_OFFSETN_SHIFT));
calData = (calData & ~_VDAC_OPA_CAL_OFFSETN_MASK)
| (init->offsetN << _VDAC_OPA_CAL_OFFSETN_SHIFT);
}
if (!init->defaultOffsetP) {
EFM_ASSERT(init->offsetP <= (_VDAC_OPA_CAL_OFFSETP_MASK
>> _VDAC_OPA_CAL_OFFSETP_SHIFT));
calData = (calData & ~_VDAC_OPA_CAL_OFFSETP_MASK)
| (init->offsetP << _VDAC_OPA_CAL_OFFSETP_SHIFT);
}
dac->OPA[opa].CAL = (calData & _VDAC_OPA_CAL_MASK);
dac->OPA[opa].MUX = (uint32_t)init->resSel
| (init->gain3xEn ? VDAC_OPA_MUX_GAIN3X : 0)
| (uint32_t)init->resInMux
| (uint32_t)init->negSel
| (uint32_t)init->posSel;
dac->OPA[opa].OUT = (uint32_t)init->outMode
| (uint32_t)init->outPen;
switch (init->drvStr) {
case opaDrvStrHigherAccHighStr:
warmupTime = 6;
break;
case opaDrvStrHighAccHighStr:
warmupTime = 8;
break;
case opaDrvStrLowAccLowStr:
warmupTime = 85;
break;
case opaDrvStrLowerAccLowStr:
default:
warmupTime = 100;
break;
}
dac->OPA[opa].TIMER = (uint32_t)(init->settleTime
<< _VDAC_OPA_TIMER_SETTLETIME_SHIFT)
| (uint32_t)(warmupTime
<< _VDAC_OPA_TIMER_WARMUPTIME_SHIFT)
| (uint32_t)(init->startupDly
<< _VDAC_OPA_TIMER_STARTUPDLY_SHIFT);
dac->OPA[opa].CTRL = (init->aportYMasterDisable
? VDAC_OPA_CTRL_APORTYMASTERDIS : 0)
| (init->aportXMasterDisable
? VDAC_OPA_CTRL_APORTXMASTERDIS : 0)
| (uint32_t)init->prsOutSel
| (uint32_t)init->prsSel
| (uint32_t)init->prsMode
| (init->prsEn ? VDAC_OPA_CTRL_PRSEN : 0)
| (init->halfDrvStr
? VDAC_OPA_CTRL_OUTSCALE_HALF
: VDAC_OPA_CTRL_OUTSCALE_FULL)
| (init->hcmDisable ? VDAC_OPA_CTRL_HCMDIS : 0)
| (init->ugBwScale ? VDAC_OPA_CTRL_INCBW : 0)
| (uint32_t)init->drvStr;
if (opa == OPA0) {
#if defined(VDAC_STATUS_OPA0ENS)
dac->CMD |= VDAC_CMD_OPA0EN;
#endif
#if defined(VDAC_STATUS_OPA1ENS)
} else if (opa == OPA1) {
dac->CMD |= VDAC_CMD_OPA1EN;
#endif
#if defined(VDAC_STATUS_OPA2ENS)
} else if (opa == OPA2) {
dac->CMD |= VDAC_CMD_OPA2EN;
#endif
#if defined(VDAC_STATUS_OPA3ENS)
} else { /* OPA3 */
dac->CMD |= VDAC_CMD_OPA3EN;
#endif
}
#endif
}
/** @} (end addtogroup opamp) */
#endif /* (defined(OPAMP_PRESENT) && (OPAMP_COUNT == 1)
|| defined(VDAC_PRESENT) && (VDAC_COUNT > 0) */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,661 @@
/***************************************************************************//**
* @file
* @brief Peripheral Reflex System (PRS) Peripheral API
*******************************************************************************
* # 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_prs.h"
#if defined(PRS_COUNT) && (PRS_COUNT > 0)
#include "sl_assert.h"
/***************************************************************************//**
* @addtogroup prs PRS - Peripheral Reflex System
* @brief Peripheral Reflex System (PRS) Peripheral API
* @details
* This module contains functions to control the PRS peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The PRS allows configurable, fast, and autonomous
* communication between peripherals on the MCU or SoC.
* @{
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/* Generic defines for async and sync signals applying to all TIMER instances.
* Those defines map to TIMER2 but it could be any TIMER instance number. */
#define _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERUF _PRS_ASYNC_CH_CTRL_SIGSEL_TIMER2UF
#define _PRS_ASYNC_CH_CTRL_SIGSEL_TIMEROF _PRS_ASYNC_CH_CTRL_SIGSEL_TIMER2OF
#define _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERCC0 _PRS_ASYNC_CH_CTRL_SIGSEL_TIMER2CC0
#define _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERCC1 _PRS_ASYNC_CH_CTRL_SIGSEL_TIMER2CC1
#define _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERCC2 _PRS_ASYNC_CH_CTRL_SIGSEL_TIMER2CC2
#define _PRS_SYNC_CH_CTRL_SIGSEL_TIMERUF _PRS_SYNC_CH_CTRL_SIGSEL_TIMER2UF
#define _PRS_SYNC_CH_CTRL_SIGSEL_TIMEROF _PRS_SYNC_CH_CTRL_SIGSEL_TIMER2OF
#define _PRS_SYNC_CH_CTRL_SIGSEL_TIMERCC0 _PRS_SYNC_CH_CTRL_SIGSEL_TIMER2CC0
#define _PRS_SYNC_CH_CTRL_SIGSEL_TIMERCC1 _PRS_SYNC_CH_CTRL_SIGSEL_TIMER2CC1
#define _PRS_SYNC_CH_CTRL_SIGSEL_TIMERCC2 _PRS_SYNC_CH_CTRL_SIGSEL_TIMER2CC2
/*******************************************************************************
************************** LOCAL FUNCTIONS ********************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Get PRS source signal for a channel.
*
* @param[in] type
* PRS channel type. This can be either @ref prsTypeAsync or
* @ref prsTypeSync.
*
* @param[in] ch
* channel number.
*
* @return
* PRS signal assigned to the channel.
******************************************************************************/
static PRS_Signal_t getSignal(unsigned int ch, PRS_ChType_t type)
{
PRS_Signal_t signal;
#if defined(_PRS_ASYNC_CH_CTRL_SOURCESEL_MASK)
if (type == prsTypeAsync) {
signal = (PRS_Signal_t) (PRS->ASYNC_CH[ch].CTRL
& (_PRS_ASYNC_CH_CTRL_SOURCESEL_MASK | _PRS_ASYNC_CH_CTRL_SIGSEL_MASK));
} else {
signal = (PRS_Signal_t) (PRS->SYNC_CH[ch].CTRL
& (_PRS_SYNC_CH_CTRL_SOURCESEL_MASK | _PRS_SYNC_CH_CTRL_SIGSEL_MASK));
}
#else
(void) type;
signal = (PRS_Signal_t) (PRS->CH[ch].CTRL
& (_PRS_CH_CTRL_SOURCESEL_MASK | _PRS_CH_CTRL_SIGSEL_MASK));
#endif
return signal;
}
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
#if defined(_SILICON_LABS_32B_SERIES_2)
/***************************************************************************//**
* @brief
* Convert an async PRS source to a sync source.
*
* @details
* This conversion must be done because the id's of the same peripheral
* source is different depending on if it's used as an asynchronous PRS source
* or a synchronous PRS source.
*
* @param[in] asyncSource
* The id of the asynchronous PRS source.
*
* @return
* The id of the corresponding synchronous PRS source.
******************************************************************************/
uint32_t PRS_ConvertToSyncSource(uint32_t asyncSource)
{
uint32_t syncSource = 0;
switch (asyncSource) {
case _PRS_ASYNC_CH_CTRL_SOURCESEL_NONE:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_NONE;
break;
#if defined(IADC_PRESENT)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_IADC0:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_IADC0;
break;
#endif
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER0:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER0;
break;
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER1:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER1;
break;
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER2:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER2;
break;
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER3:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER3;
break;
#if defined(TIMER4)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER4:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER4;
break;
#endif
#if defined(TIMER5)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER5:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER5;
break;
#endif
#if defined(TIMER6)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER6:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER6;
break;
#endif
#if defined(TIMER7)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER7:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_TIMER7;
break;
#endif
#if defined(VDAC0)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_VDAC0L:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_VDAC0;
break;
#endif
#if defined(VDAC1)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_VDAC1L:
syncSource = _PRS_SYNC_CH_CTRL_SOURCESEL_VDAC1;
break;
#endif
default:
EFM_ASSERT(false);
break;
}
return syncSource;
}
/***************************************************************************//**
* @brief
* Convert an async PRS signal to a sync signal.
*
* @details
* PRS values for some peripherals signals differ between asynchronous and
* synchronous PRS channels. This function must be used to handle the
* conversion.
*
* @param[in] asyncSource
* The id of the asynchronous PRS source.
*
* @param[in] asyncSignal
* The id of the asynchronous PRS signal.
*
* @return
* The id of the corresponding synchronous PRS signal.
******************************************************************************/
uint32_t PRS_ConvertToSyncSignal(uint32_t asyncSource, uint32_t asyncSignal)
{
uint32_t syncSignal = asyncSignal;
switch (asyncSource) {
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER0:
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER1:
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER2:
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER3:
#if defined(_PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER4)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER4:
#endif
#if defined(_PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER5)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER5:
#endif
#if defined(_PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER6)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER6:
#endif
#if defined(_PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER7)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_TIMER7:
#endif
/* Async and sync signal values are consistent across all timers instances.
* Generic defines are used. */
switch (asyncSignal) {
case _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERUF:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_TIMERUF;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_TIMEROF:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_TIMEROF;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERCC0:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_TIMERCC0;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERCC1:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_TIMERCC1;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_TIMERCC2:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_TIMERCC2;
break;
default:
EFM_ASSERT(false);
break;
}
break;
#if defined(IADC0)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_IADC0:
switch (asyncSignal) {
case _PRS_ASYNC_CH_CTRL_SIGSEL_IADC0SCANENTRYDONE:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_IADC0SCANENTRYDONE;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_IADC0SCANTABLEDONE:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_IADC0SCANTABLEDONE;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_IADC0SINGLEDONE:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_IADC0SINGLEDONE;
break;
default:
EFM_ASSERT(false);
break;
}
break;
#endif
#if defined(VDAC0)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_VDAC0L:
switch (asyncSignal) {
case _PRS_ASYNC_CH_CTRL_SIGSEL_VDAC0LCH0DONEASYNC:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_VDAC0CH0DONESYNC;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_VDAC0LCH1DONEASYNC:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_VDAC0CH1DONESYNC;
break;
default:
EFM_ASSERT(false);
break;
}
break;
#endif
#if defined(VDAC1)
case _PRS_ASYNC_CH_CTRL_SOURCESEL_VDAC1L:
switch (asyncSignal) {
case _PRS_ASYNC_CH_CTRL_SIGSEL_VDAC1LCH0DONEASYNC:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_VDAC1CH0DONESYNC;
break;
case _PRS_ASYNC_CH_CTRL_SIGSEL_VDAC1LCH1DONEASYNC:
syncSignal = _PRS_SYNC_CH_CTRL_SIGSEL_VDAC1CH1DONESYNC;
break;
default:
EFM_ASSERT(false);
break;
}
break;
#endif
default:
// No translation
break;
}
return syncSignal;
}
#endif
/***************************************************************************//**
* @brief
* Set a source and signal for a channel.
*
* @param[in] ch
* A channel to define the signal and source for.
*
* @param[in] source
* A source to select for the channel. Use one of PRS_CH_CTRL_SOURCESEL_x defines.
*
* @param[in] signal
* A signal (for selected @p source) to use. Use one of PRS_CH_CTRL_SIGSEL_x
* defines.
*
* @param[in] edge
* An edge (for selected source/signal) to generate the pulse for.
******************************************************************************/
void PRS_SourceSignalSet(unsigned int ch,
uint32_t source,
uint32_t signal,
PRS_Edge_TypeDef edge)
{
#if defined(_PRS_SYNC_CH_CTRL_MASK)
(void) edge;
EFM_ASSERT(ch < PRS_SYNC_CHAN_COUNT);
PRS->SYNC_CH[ch].CTRL = (source & _PRS_SYNC_CH_CTRL_SOURCESEL_MASK)
| (signal & _PRS_SYNC_CH_CTRL_SIGSEL_MASK);
#else
EFM_ASSERT(ch < PRS_CHAN_COUNT);
PRS->CH[ch].CTRL = (source & _PRS_CH_CTRL_SOURCESEL_MASK)
| (signal & _PRS_CH_CTRL_SIGSEL_MASK)
| (uint32_t)edge << _PRS_CH_CTRL_EDSEL_SHIFT;
#endif
}
#if defined(PRS_ASYNC_SUPPORTED)
/***************************************************************************//**
* @brief
* Set the source and asynchronous signal for a channel.
*
* @details
* Asynchronous reflexes are not clocked on HFPERCLK and can be used even in
* EM2/EM3.
* There is a limitation to reflexes operating in asynchronous mode in
* that they can only be used by a subset of the reflex consumers. See
* the PRS chapter in the reference manual for the complete list of
* supported asynchronous signals and consumers.
*
* @note
* This function is not supported on EFM32GxxxFyyy parts.
* In asynchronous mode, the edge detector only works in EM0 and should
* not be used. The EDSEL parameter in PRS_CHx_CTRL register is set to 0 (OFF)
* by default.
*
* @param[in] ch
* A channel to define the source and asynchronous signal for.
*
* @param[in] source
* A source to select for the channel. Use one of PRS_CH_CTRL_SOURCESEL_x defines.
*
* @param[in] signal
* An asynchronous signal (for selected @p source) to use. Use one of the
* PRS_CH_CTRL_SIGSEL_x defines that support asynchronous operation.
******************************************************************************/
SL_WEAK void PRS_SourceAsyncSignalSet(unsigned int ch,
uint32_t source,
uint32_t signal)
{
PRS_ConnectSignal(ch, prsTypeAsync, (PRS_Signal_t) (source | signal));
}
#endif
#if defined(_PRS_ROUTELOC0_MASK) || (defined(_PRS_ROUTE_MASK) && (_PRS_ROUTE_MASK))
/***************************************************************************//**
* @brief
* Send the output of a PRS channel to a GPIO pin.
*
* @details
* This function is used to send the output of a PRS channel to a GPIO pin.
* Note that there are certain restrictions to where a PRS channel can be
* routed. Consult the datasheet of the device to see if a channel can be
* routed to the requested GPIO pin.
*
* @param[in] ch
* PRS channel number.
*
* @param[in] location
* PRS routing location.
******************************************************************************/
void PRS_GpioOutputLocation(unsigned int ch,
unsigned int location)
{
EFM_ASSERT(ch < PRS_CHAN_COUNT);
#if defined(_PRS_ROUTE_MASK)
PRS->ROUTE |= (location << _PRS_ROUTE_LOCATION_SHIFT)
| (1 << ch);
#else
uint32_t shift = (ch % 4) * 8;
uint32_t mask = location << shift;
uint32_t locationGroup = ch / 4;
/* Since all ROUTELOCx registers are in consecutive memory locations, treat them
* as an array starting at ROUTELOC0 and use locationGroup to index into this array */
volatile uint32_t * routeloc = &PRS->ROUTELOC0;
routeloc[locationGroup] |= mask;
PRS->ROUTEPEN |= 1 << ch;
#endif
}
#endif
/***************************************************************************//**
* @brief
* Search for the first free PRS channel.
*
* @param[in] type
* PRS channel type. This can be either @ref prsTypeAsync or
* @ref prsTypeSync.
*
* @return
* Channel number >= 0 if an unused PRS channel was found. If no free PRS
* channel was found then -1 is returned.
******************************************************************************/
SL_WEAK int PRS_GetFreeChannel(PRS_ChType_t type)
{
int ch = -1;
PRS_Signal_t signal;
int max;
if (type == prsTypeAsync) {
max = PRS_ASYNC_CHAN_COUNT;
} else {
max = PRS_SYNC_CHAN_COUNT;
}
for (int i = 0; i < max; i++) {
signal = getSignal(i, type);
if (signal == prsSignalNone) {
ch = i;
break;
}
}
return ch;
}
/***************************************************************************//**
* @brief
* Reset all PRS channels
*
* @details
* This function will reset all the PRS channel configuration.
******************************************************************************/
void PRS_Reset(void)
{
unsigned int i;
#if defined(_SILICON_LABS_32B_SERIES_2)
PRS->ASYNC_SWLEVEL = 0;
for (i = 0; i < PRS_ASYNC_CHAN_COUNT; i++) {
PRS->ASYNC_CH[i].CTRL = _PRS_ASYNC_CH_CTRL_RESETVALUE;
}
for (i = 0; i < PRS_SYNC_CHAN_COUNT; i++) {
PRS->SYNC_CH[i].CTRL = _PRS_SYNC_CH_CTRL_RESETVALUE;
}
#else
PRS->SWLEVEL = 0x0;
for (i = 0; i < PRS_CHAN_COUNT; i++) {
PRS->CH[i].CTRL = _PRS_CH_CTRL_RESETVALUE;
}
#endif
}
/***************************************************************************//**
* @brief
* Connect a PRS signal to a channel.
*
* @details
* This function will make the PRS signal available on the specific channel.
* Only a single PRS signal can be connected to any given channel.
*
* @param[in] ch
* PRS channel number.
*
* @param[in] type
* PRS channel type. This can be either @ref prsTypeAsync or
* @ref prsTypeSync.
*
* @param[in] signal
* This is the PRS signal that should be placed on the channel.
******************************************************************************/
void PRS_ConnectSignal(unsigned int ch, PRS_ChType_t type, PRS_Signal_t signal)
{
#if defined(_PRS_ASYNC_CH_CTRL_MASK)
// Series 2 devices
uint32_t sourceField = ((uint32_t)signal & _PRS_ASYNC_CH_CTRL_SOURCESEL_MASK)
>> _PRS_ASYNC_CH_CTRL_SOURCESEL_SHIFT;
uint32_t signalField = ((uint32_t)signal & _PRS_ASYNC_CH_CTRL_SIGSEL_MASK)
>> _PRS_ASYNC_CH_CTRL_SIGSEL_SHIFT;
if (type == prsTypeAsync) {
EFM_ASSERT(ch < PRS_ASYNC_CHAN_COUNT);
PRS->ASYNC_CH[ch].CTRL = PRS_ASYNC_CH_CTRL_FNSEL_A
| (sourceField << _PRS_ASYNC_CH_CTRL_SOURCESEL_SHIFT)
| (signalField << _PRS_ASYNC_CH_CTRL_SIGSEL_SHIFT);
} else {
EFM_ASSERT(ch < PRS_SYNC_CHAN_COUNT);
signalField = PRS_ConvertToSyncSignal(sourceField, signalField);
sourceField = PRS_ConvertToSyncSource(sourceField);
PRS->SYNC_CH[ch].CTRL = (sourceField << _PRS_SYNC_CH_CTRL_SOURCESEL_SHIFT)
| (signalField << _PRS_SYNC_CH_CTRL_SIGSEL_SHIFT);
}
#else
// Series 0 and Series 1 devices
uint32_t signalField = (uint32_t) signal & (_PRS_CH_CTRL_SOURCESEL_MASK
| _PRS_CH_CTRL_SIGSEL_MASK);
if (type == prsTypeAsync) {
#if defined(PRS_ASYNC_SUPPORTED)
EFM_ASSERT(ch < PRS_ASYNC_CHAN_COUNT);
PRS->CH[ch].CTRL = PRS_CH_CTRL_EDSEL_OFF
| PRS_CH_CTRL_ASYNC
| signalField;
#endif
} else {
EFM_ASSERT(ch < PRS_SYNC_CHAN_COUNT);
PRS->CH[ch].CTRL = PRS_CH_CTRL_EDSEL_OFF
| signalField;
}
#endif
}
#if defined(_SILICON_LABS_32B_SERIES_2)
/***************************************************************************//**
* @brief
* Connect a peripheral consumer to a PRS channel.
*
* @details
* Different peripherals can use PRS channels as their input. This function
* can be used to connect a peripheral consumer to a PRS channel. Multiple
* consumers can be connected to a single PRS channel.
*
* @param[in] ch
* PRS channel number.
*
* @param[in] type
* PRS channel type. This can be either @ref prsTypeAsync or
* @ref prsTypeSync.
*
* @param[in] consumer
* This is the PRS consumer.
******************************************************************************/
SL_WEAK void PRS_ConnectConsumer(unsigned int ch, PRS_ChType_t type, PRS_Consumer_t consumer)
{
EFM_ASSERT((uint32_t)consumer <= 0xFFF);
volatile uint32_t * addr = (volatile uint32_t *) PRS;
uint32_t offset = (uint32_t) consumer;
addr = addr + offset / 4;
if (consumer != prsConsumerNone) {
if (type == prsTypeAsync) {
*addr = ch << _PRS_CONSUMER_TIMER0_CC0_PRSSEL_SHIFT;
} else {
*addr = ch << _PRS_CONSUMER_TIMER0_CC0_SPRSSEL_SHIFT;
}
}
}
/***************************************************************************//**
* @brief
* Send the output of a PRS channel to a GPIO pin.
*
* @details
* This function is used to send the output of a PRS channel to a GPIO pin.
* Note that there are certain restrictions to where a PRS channel can be
* routed. Consult the datasheet of the device to see if a channel can be
* routed to the requested GPIO pin. Some devices for instance can only route
* the async channels 0-5 on GPIO pins PAx and PBx while async channels 6-11
* can only be routed to GPIO pins PCx and PDx
*
* @param[in] ch
* PRS channel number.
*
* @param[in] type
* PRS channel type. This can be either @ref prsTypeAsync or
* @ref prsTypeSync.
*
* @param[in] port
* GPIO port
*
* @param[in] pin
* GPIO pin
******************************************************************************/
SL_WEAK void PRS_PinOutput(unsigned int ch, PRS_ChType_t type, GPIO_Port_TypeDef port, uint8_t pin)
{
volatile uint32_t * addr;
if (type == prsTypeAsync) {
addr = &GPIO->PRSROUTE[0].ASYNCH0ROUTE;
} else {
addr = &GPIO->PRSROUTE[0].SYNCH0ROUTE;
}
addr += ch;
*addr = ((uint32_t)port << _GPIO_PRS_ASYNCH0ROUTE_PORT_SHIFT)
| ((uint32_t)pin << _GPIO_PRS_ASYNCH0ROUTE_PIN_SHIFT);
if (type == prsTypeAsync) {
GPIO->PRSROUTE[0].ROUTEEN |= 0x1 << (ch + _GPIO_PRS_ROUTEEN_ASYNCH0PEN_SHIFT);
} else {
GPIO->PRSROUTE[0].ROUTEEN |= 0x1 << (ch + _GPIO_PRS_ROUTEEN_SYNCH0PEN_SHIFT);
}
}
/***************************************************************************//**
* @brief
* Combine two PRS channels using a logic function.
*
* @details
* This function allows you to combine the output of one PRS channel with the
* the signal of another PRS channel using various logic functions. Note that
* for series 2, config 1 devices, the hardware only allows a PRS channel to
* be combined with the previous channel. So for instance channel 5 can be
* combined only with channel 4.
*
* The logic function operates on two PRS channels called A and B. The output
* of PRS channel B is combined with the PRS source configured for channel A
* to produce an output. This output is used as the output of channel A.
*
* @param[in] chA
* PRS Channel for the A input.
*
* @param[in] chB
* PRS Channel for the B input.
*
* @param[in] logic
* The logic function to use when combining the Channel A and Channel B. The
* output of the logic function is the output of Channel A. Function like
* AND, OR, XOR, NOT and more are available.
******************************************************************************/
SL_WEAK void PRS_Combine(unsigned int chA, unsigned int chB, PRS_Logic_t logic)
{
EFM_ASSERT(chA < PRS_ASYNC_CHAN_COUNT);
EFM_ASSERT(chB < PRS_ASYNC_CHAN_COUNT);
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1)
EFM_ASSERT(chA == ((chB + 1) % PRS_ASYNC_CHAN_COUNT));
PRS->ASYNC_CH[chA].CTRL = (PRS->ASYNC_CH[chA].CTRL & ~_PRS_ASYNC_CH_CTRL_FNSEL_MASK)
| ((uint32_t)logic << _PRS_ASYNC_CH_CTRL_FNSEL_SHIFT);
#else
PRS->ASYNC_CH[chA].CTRL = (PRS->ASYNC_CH[chA].CTRL
& ~(_PRS_ASYNC_CH_CTRL_FNSEL_MASK
| _PRS_ASYNC_CH_CTRL_AUXSEL_MASK))
| ((uint32_t)logic << _PRS_ASYNC_CH_CTRL_FNSEL_SHIFT)
| ((uint32_t)chB << _PRS_ASYNC_CH_CTRL_AUXSEL_SHIFT);
#endif
}
#endif
/** @} (end addtogroup prs) */
#endif /* defined(PRS_COUNT) && (PRS_COUNT > 0) */

View File

@@ -0,0 +1,402 @@
/***************************************************************************//**
* @file
* @brief Reset Management Unit (RMU) peripheral module peripheral API
*******************************************************************************
* # 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_rmu.h"
#if (defined(RMU_COUNT) && (RMU_COUNT > 0)) || (_EMU_RSTCTRL_MASK)
#include "sl_common.h"
#include "em_emu.h"
#include "em_bus.h"
/***************************************************************************//**
* @addtogroup rmu RMU - Reset Management Unit
* @brief Reset Management Unit (RMU) Peripheral API
* @details
* This module contains functions to control the RMU peripheral of Silicon
* Labs 32-bit MCUs and SoCs. RMU ensures correct reset operation and is
* responsible for connecting the different reset sources to the reset lines of
* the MCU or SoC.
* @{
******************************************************************************/
/*******************************************************************************
***************************** DEFINES *********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
#if defined(_RMU_RSTCAUSE_MASK)
/** Reset cause XMASKS for series-0 and series-1 devices.
Reset cause zero and "don't care" bit definitions (XMASKs).
An XMASK 1 bit marks a bit that must be zero in RMU_RSTCAUSE. A 0 in XMASK
is a "don't care" bit in RMU_RSTCAUSE if also 0 in resetCauseMask
in @ref RMU_ResetCauseMasks_Typedef. */
/* EFM32G */
#if (_RMU_RSTCAUSE_MASK == 0x0000007FUL)
#define RMU_RSTCAUSE_PORST_XMASK 0x00000000UL /** 0000000000000000 < Power On Reset */
#define RMU_RSTCAUSE_BODUNREGRST_XMASK 0x00000001UL /** 0000000000000001 < Brown Out Detector Unregulated Domain Reset */
#define RMU_RSTCAUSE_BODREGRST_XMASK 0x0000001BUL /** 0000000000011011 < Brown Out Detector Regulated Domain Reset */
#define RMU_RSTCAUSE_EXTRST_XMASK 0x00000003UL /** 0000000000000011 < External Pin Reset */
#define RMU_RSTCAUSE_WDOGRST_XMASK 0x00000003UL /** 0000000000000011 < Watchdog Reset */
#define RMU_RSTCAUSE_LOCKUPRST_XMASK 0x0000001FUL /** 0000000000011111 < LOCKUP Reset */
#define RMU_RSTCAUSE_SYSREQRST_XMASK 0x0000001FUL /** 0000000000011111 < System Request Reset */
#define NUM_RSTCAUSES 7
/* EFM32TG, EFM32HG, EZR32HG, EFM32ZG */
#elif (_RMU_RSTCAUSE_MASK == 0x000007FFUL)
#define RMU_RSTCAUSE_PORST_XMASK 0x00000000UL /** 0000000000000000 < Power On Reset */
#define RMU_RSTCAUSE_BODUNREGRST_XMASK 0x00000081UL /** 0000000010000001 < Brown Out Detector Unregulated Domain Reset */
#define RMU_RSTCAUSE_BODREGRST_XMASK 0x00000091UL /** 0000000010010001 < Brown Out Detector Regulated Domain Reset */
#define RMU_RSTCAUSE_EXTRST_XMASK 0x00000001UL /** 0000000000000001 < External Pin Reset */
#define RMU_RSTCAUSE_WDOGRST_XMASK 0x00000003UL /** 0000000000000011 < Watchdog Reset */
#define RMU_RSTCAUSE_LOCKUPRST_XMASK 0x0000EFDFUL /** 1110111111011111 < LOCKUP Reset */
#define RMU_RSTCAUSE_SYSREQRST_XMASK 0x0000EF9FUL /** 1110111110011111 < System Request Reset */
#define RMU_RSTCAUSE_EM4RST_XMASK 0x00000719UL /** 0000011100011001 < EM4 Reset */
#define RMU_RSTCAUSE_EM4WURST_XMASK 0x00000619UL /** 0000011000011001 < EM4 Wake-up Reset */
#define RMU_RSTCAUSE_BODAVDD0_XMASK 0x0000041FUL /** 0000010000011111 < AVDD0 Bod Reset. */
#define RMU_RSTCAUSE_BODAVDD1_XMASK 0x0000021FUL /** 0000001000011111 < AVDD1 Bod Reset. */
#define NUM_RSTCAUSES 11
/* EFM32GG, EFM32LG, EZR32LG, EFM32WG, EZR32WG */
#elif (_RMU_RSTCAUSE_MASK == 0x0000FFFFUL)
#define RMU_RSTCAUSE_PORST_XMASK 0x00000000UL /** 0000000000000000 < Power On Reset */
#define RMU_RSTCAUSE_BODUNREGRST_XMASK 0x00000081UL /** 0000000010000001 < Brown Out Detector Unregulated Domain Reset */
#define RMU_RSTCAUSE_BODREGRST_XMASK 0x00000091UL /** 0000000010010001 < Brown Out Detector Regulated Domain Reset */
#define RMU_RSTCAUSE_EXTRST_XMASK 0x00000001UL /** 0000000000000001 < External Pin Reset */
#define RMU_RSTCAUSE_WDOGRST_XMASK 0x00000003UL /** 0000000000000011 < Watchdog Reset */
#define RMU_RSTCAUSE_LOCKUPRST_XMASK 0x0000EFDFUL /** 1110111111011111 < LOCKUP Reset */
#define RMU_RSTCAUSE_SYSREQRST_XMASK 0x0000EF9FUL /** 1110111110011111 < System Request Reset */
#define RMU_RSTCAUSE_EM4RST_XMASK 0x00000719UL /** 0000011100011001 < EM4 Reset */
#define RMU_RSTCAUSE_EM4WURST_XMASK 0x00000619UL /** 0000011000011001 < EM4 Wake-up Reset */
#define RMU_RSTCAUSE_BODAVDD0_XMASK 0x0000041FUL /** 0000010000011111 < AVDD0 Bod Reset */
#define RMU_RSTCAUSE_BODAVDD1_XMASK 0x0000021FUL /** 0000001000011111 < AVDD1 Bod Reset */
#define RMU_RSTCAUSE_BUBODVDDDREG_XMASK 0x00000001UL /** 0000000000000001 < Backup Brown Out Detector, VDD_DREG */
#define RMU_RSTCAUSE_BUBODBUVIN_XMASK 0x00000001UL /** 0000000000000001 < Backup Brown Out Detector, BU_VIN */
#define RMU_RSTCAUSE_BUBODUNREG_XMASK 0x00000001UL /** 0000000000000001 < Backup Brown Out Detector Unregulated Domain */
#define RMU_RSTCAUSE_BUBODREG_XMASK 0x00000001UL /** 0000000000000001 < Backup Brown Out Detector Regulated Domain */
#define RMU_RSTCAUSE_BUMODERST_XMASK 0x00000001UL /** 0000000000000001 < Backup mode reset */
#define NUM_RSTCAUSES 16
/* EFM32xG1, EFM32xG12, EFM32xG13 */
#elif ((_RMU_RSTCAUSE_MASK & 0x0FFFFFFF) == 0x00010F1DUL)
#define RMU_RSTCAUSE_PORST_XMASK 0x00000000UL /** 0000000000000000 < Power On Reset */
#define RMU_RSTCAUSE_BODAVDD_XMASK 0x00000001UL /** 0000000000000001 < AVDD BOD Reset */
#define RMU_RSTCAUSE_BODDVDD_XMASK 0x00000001UL /** 0000000000000001 < DVDD BOD Reset */
#define RMU_RSTCAUSE_BODREGRST_XMASK 0x00000001UL /** 0000000000000001 < Regulated Domain (DEC) BOD Reset */
#define RMU_RSTCAUSE_EXTRST_XMASK 0x00000001UL /** 0000000000000001 < External Pin Reset */
#define RMU_RSTCAUSE_LOCKUPRST_XMASK 0x0000001DUL /** 0000000000011101 < LOCKUP Reset */
#define RMU_RSTCAUSE_SYSREQRST_XMASK 0x0000001DUL /** 0000000000011101 < System Request Reset */
#define RMU_RSTCAUSE_WDOGRST_XMASK 0x0000001DUL /** 0000000000011101 < Watchdog Reset */
#define RMU_RSTCAUSE_EM4RST_XMASK 0x0000001DUL /** 0000000000011101 < EM4H/S Reset */
#define NUM_RSTCAUSES 9
/* EFM32GG11 */
#elif ((_RMU_RSTCAUSE_MASK & 0x0FFFFFFF) == 0x00011F1DUL)
#define RMU_RSTCAUSE_PORST_XMASK 0x00000000UL /** 0000000000000000 < Power On Reset */
#define RMU_RSTCAUSE_BODAVDD_XMASK 0x00000001UL /** 0000000000000001 < AVDD BOD Reset */
#define RMU_RSTCAUSE_BODDVDD_XMASK 0x00000001UL /** 0000000000000001 < DVDD BOD Reset */
#define RMU_RSTCAUSE_BODREGRST_XMASK 0x00000001UL /** 0000000000000001 < Regulated Domain (DEC) BOD Reset */
#define RMU_RSTCAUSE_EXTRST_XMASK 0x00000001UL /** 0000000000000001 < External Pin Reset */
#define RMU_RSTCAUSE_LOCKUPRST_XMASK 0x0000001DUL /** 0000000000011101 < LOCKUP Reset */
#define RMU_RSTCAUSE_SYSREQRST_XMASK 0x0000001DUL /** 0000000000011101 < System Request Reset */
#define RMU_RSTCAUSE_WDOGRST_XMASK 0x0000001DUL /** 0000000000011101 < Watchdog Reset */
#define RMU_RSTCAUSE_BUMODERST_XMASK 0x0000001DUL /** 0000000000011101 < Backup mode reset */
#define RMU_RSTCAUSE_EM4RST_XMASK 0x0000001DUL /** 0000000000011101 < EM4H/S Reset */
#define NUM_RSTCAUSES 10
#else
#error "RMU_RSTCAUSE XMASKs are not defined for this family."
#endif
/* Pin reset definitions. */
#define LB_CLW0 (*((volatile uint32_t *)(LOCKBITS_BASE) +122))
#define LB_CLW0_PINRESETSOFT (1 << 2)
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80)
/* Fix for errata EMU_E208 - Occasional Full Reset After Exiting EM4H. */
#define ERRATA_FIX_EMU_E208_EN
#endif
#endif /* #if defined(_RMU_RSTCAUSE_MASK) */
/*******************************************************************************
******************************* STRUCTS ***********************************
******************************************************************************/
#if defined(_RMU_RSTCAUSE_MASK)
/** Reset cause mask type for series-0 and series-1 devices. */
typedef struct {
/** Reset cause 1 bits. */
uint32_t resetCauseMask;
/** Reset cause 0 and "don't care" bits. */
uint32_t resetCauseZeroXMask;
} RMU_ResetCauseMasks_Typedef;
#endif
/*******************************************************************************
******************************* TYPEDEFS **********************************
******************************************************************************/
#if defined(_RMU_RSTCAUSE_MASK)
/** Reset cause mask table. */
static const RMU_ResetCauseMasks_Typedef resetCauseMasks[NUM_RSTCAUSES] =
{
{ RMU_RSTCAUSE_PORST, RMU_RSTCAUSE_PORST_XMASK },
#if defined(RMU_RSTCAUSE_BODUNREGRST)
{ RMU_RSTCAUSE_BODUNREGRST, RMU_RSTCAUSE_BODUNREGRST_XMASK },
#endif
#if defined(RMU_RSTCAUSE_BODREGRST)
{ RMU_RSTCAUSE_BODREGRST, RMU_RSTCAUSE_BODREGRST_XMASK },
#endif
#if defined(RMU_RSTCAUSE_AVDDBOD)
{ RMU_RSTCAUSE_AVDDBOD, RMU_RSTCAUSE_BODAVDD_XMASK },
#endif
#if defined(RMU_RSTCAUSE_DVDDBOD)
{ RMU_RSTCAUSE_DVDDBOD, RMU_RSTCAUSE_BODDVDD_XMASK },
#endif
#if defined(RMU_RSTCAUSE_DECBOD)
{ RMU_RSTCAUSE_DECBOD, RMU_RSTCAUSE_BODREGRST_XMASK },
#endif
{ RMU_RSTCAUSE_EXTRST, RMU_RSTCAUSE_EXTRST_XMASK },
{ RMU_RSTCAUSE_WDOGRST, RMU_RSTCAUSE_WDOGRST_XMASK },
{ RMU_RSTCAUSE_LOCKUPRST, RMU_RSTCAUSE_LOCKUPRST_XMASK },
{ RMU_RSTCAUSE_SYSREQRST, RMU_RSTCAUSE_SYSREQRST_XMASK },
#if defined(RMU_RSTCAUSE_EM4RST)
{ RMU_RSTCAUSE_EM4RST, RMU_RSTCAUSE_EM4RST_XMASK },
#endif
#if defined(RMU_RSTCAUSE_EM4WURST)
{ RMU_RSTCAUSE_EM4WURST, RMU_RSTCAUSE_EM4WURST_XMASK },
#endif
#if defined(RMU_RSTCAUSE_BODAVDD0)
{ RMU_RSTCAUSE_BODAVDD0, RMU_RSTCAUSE_BODAVDD0_XMASK },
#endif
#if defined(RMU_RSTCAUSE_BODAVDD1)
{ RMU_RSTCAUSE_BODAVDD1, RMU_RSTCAUSE_BODAVDD1_XMASK },
#endif
#if defined(BU_PRESENT) && defined(_SILICON_LABS_32B_SERIES_0)
{ RMU_RSTCAUSE_BUBODVDDDREG, RMU_RSTCAUSE_BUBODVDDDREG_XMASK },
{ RMU_RSTCAUSE_BUBODBUVIN, RMU_RSTCAUSE_BUBODBUVIN_XMASK },
{ RMU_RSTCAUSE_BUBODUNREG, RMU_RSTCAUSE_BUBODUNREG_XMASK },
{ RMU_RSTCAUSE_BUBODREG, RMU_RSTCAUSE_BUBODREG_XMASK },
{ RMU_RSTCAUSE_BUMODERST, RMU_RSTCAUSE_BUMODERST_XMASK },
#elif defined(RMU_RSTCAUSE_BUMODERST)
{ RMU_RSTCAUSE_BUMODERST, RMU_RSTCAUSE_BUMODERST_XMASK },
#endif
};
#endif /* #if defined(_RMU_RSTCAUSE_MASK) */
/*******************************************************************************
******************************** TEST ********************************
******************************************************************************/
#if defined(EMLIB_REGRESSION_TEST)
/* A test variable that replaces the RSTCAUSE cause register when testing
the RMU_ResetCauseGet function. */
extern uint32_t rstCause;
#else
static uint32_t rstCause = UINT32_MAX;
#endif
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Disable/enable reset for various peripherals and signal sources.
*
* @param[in] reset Reset types to enable/disable.s
*
* @param[in] mode Reset mode.
******************************************************************************/
#if defined(__GNUC__) && __GNUC__ >= 11
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wanalyzer-shift-count-overflow"
#endif
void RMU_ResetControl(RMU_Reset_TypeDef reset, RMU_ResetMode_TypeDef mode)
{
/* Note that the RMU supports bit-band access, but not peripheral bit-field set/clear. */
#if defined(_RMU_CTRL_PINRMODE_MASK)
uint32_t val;
#endif
uint32_t shift;
shift = SL_CTZ((uint32_t)reset);
#if defined(_EMU_RSTCTRL_MASK)
BUS_RegBitWrite(&EMU->RSTCTRL, (uint32_t)shift, mode ? 1 : 0);
#elif defined(_RMU_CTRL_PINRMODE_MASK)
EFM_ASSERT(shift < 32);
val = (uint32_t)mode << shift;
RMU->CTRL = (RMU->CTRL & ~reset) | val;
#else
BUS_RegBitWrite(&RMU->CTRL, (uint32_t)shift, mode ? 1 : 0);
#endif
}
#if defined(__GNUC__) && __GNUC__ >= 11
#pragma GCC diagnostic pop
#endif
/***************************************************************************//**
* @brief
* Clear the reset cause register.
*
* @details
* This function clears all the reset cause bits of the RSTCAUSE register.
* The reset cause bits must be cleared by software before a new reset occurs.
* Otherwise, reset causes may accumulate. See @ref RMU_ResetCauseGet().
******************************************************************************/
void RMU_ResetCauseClear(void)
{
#if defined(_EMU_RSTCTRL_MASK)
EMU->CMD_SET = EMU_CMD_RSTCAUSECLR;
#else
RMU->CMD = RMU_CMD_RCCLR;
#endif
#if defined(EMU_AUXCTRL_HRCCLR)
{
uint32_t locked;
/* Clear reset causes not cleared with the RMU CMD register. */
/* (If EMU registers are locked, they must be unlocked first) */
locked = EMU->LOCK & EMU_LOCK_LOCKKEY_LOCKED;
if (locked) {
EMU_Unlock();
}
BUS_RegBitWrite(&(EMU->AUXCTRL), _EMU_AUXCTRL_HRCCLR_SHIFT, 1);
BUS_RegBitWrite(&(EMU->AUXCTRL), _EMU_AUXCTRL_HRCCLR_SHIFT, 0);
if (locked) {
EMU_Lock();
}
}
#endif
}
/***************************************************************************//**
* @brief
* Get the cause of the last reset.
*
* @details
* To be useful, the reset cause must be cleared by software before a new
* reset occurs. Otherwise, reset causes may accumulate. See @ref
* RMU_ResetCauseClear(). This function call will return the main cause for
* reset, which can be a bit mask (several causes) and clear away "noise".
*
* @return
* A reset cause mask. See the reference manual for a description
* of the reset cause mask.
******************************************************************************/
uint32_t RMU_ResetCauseGet(void)
{
#if !defined(EMLIB_REGRESSION_TEST)
if (rstCause != UINT32_MAX) {
// RMU_ResetCauseGet() has already been called since boot. Return what was already obtained.
return rstCause;
}
#endif
#if defined(_EMU_RSTCAUSE_MASK)
#if !defined(EMLIB_REGRESSION_TEST)
rstCause = EMU->RSTCAUSE;
#endif
return rstCause;
#endif
#if defined(_RMU_RSTCAUSE_MASK)
#if !defined(EMLIB_REGRESSION_TEST)
rstCause = RMU->RSTCAUSE;
#endif
uint32_t validRstCause = 0;
uint32_t zeroXMask;
uint32_t i;
for (i = 0; i < NUM_RSTCAUSES; i++) {
zeroXMask = resetCauseMasks[i].resetCauseZeroXMask;
#if defined(_SILICON_LABS_32B_SERIES_1)
/* Handle soft/hard pin reset. */
if (!(LB_CLW0 & LB_CLW0_PINRESETSOFT)) {
/* RSTCAUSE_EXTRST must be 0 if pin reset is configured as hard reset. */
switch (resetCauseMasks[i].resetCauseMask) {
case RMU_RSTCAUSE_LOCKUPRST:
/* Fallthrough */
case RMU_RSTCAUSE_SYSREQRST:
/* Fallthrough */
case RMU_RSTCAUSE_WDOGRST:
/* Fallthrough */
case RMU_RSTCAUSE_EM4RST:
zeroXMask |= RMU_RSTCAUSE_EXTRST;
break;
default:
/* MISRA requires a default case. */
break;
}
}
#endif
#if defined(_EMU_EM4CTRL_MASK) && defined(ERRATA_FIX_EMU_E208_EN)
/* Ignore BOD flags impacted by EMU_E208. */
if (*(volatile uint32_t *)(EMU_BASE + 0x88) & (0x1 << 8)) {
zeroXMask &= ~(RMU_RSTCAUSE_DECBOD
| RMU_RSTCAUSE_DVDDBOD
| RMU_RSTCAUSE_AVDDBOD);
}
#endif
/* Check reset cause requirements. Note that a bit is "don't care" if 0 in
both resetCauseMask and resetCauseZeroXMask. */
if ((rstCause & resetCauseMasks[i].resetCauseMask)
&& !(rstCause & zeroXMask)) {
/* Add this reset-cause to the mask of qualified reset-causes. */
validRstCause |= resetCauseMasks[i].resetCauseMask;
}
}
#if defined(_EMU_EM4CTRL_MASK) && defined(ERRATA_FIX_EMU_E208_EN)
/* Clear BOD flags impacted by EMU_E208. */
if (validRstCause & RMU_RSTCAUSE_EM4RST) {
validRstCause &= ~(RMU_RSTCAUSE_DECBOD
| RMU_RSTCAUSE_DVDDBOD
| RMU_RSTCAUSE_AVDDBOD);
}
#endif
#if !defined(EMLIB_REGRESSION_TEST)
// keep validRstCause in the static local variable for future calls
rstCause = validRstCause
#endif
return validRstCause;
#endif
}
/** @} (end addtogroup rmu) */
#endif /* defined(RMU_COUNT) && (RMU_COUNT > 0) */

View File

@@ -0,0 +1,433 @@
/***************************************************************************//**
* @file
* @brief System Peripheral API
*******************************************************************************
* # 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_system.h"
#include "sl_assert.h"
#include <stddef.h>
#if defined(SYSCFG_PRESENT)
#include "em_syscfg.h"
#endif
/***************************************************************************//**
* @addtogroup system
* @{
******************************************************************************/
/*******************************************************************************
********************************* DEFINES *********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/* Bit mask used to extract the part number value without the new naming
* bitfield. */
#define SYSCFG_CHIPREV_PARTNUMBER1 0xFE0
#define SYSCFG_CHIPREV_PARTNUMBER0 0xF
/* Bit mask to convert NON-SECURE to SECURE */
#define CONVERT_NS_TO_S (~(1 << 28U))
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Get a chip major/minor revision.
*
* @param[out] rev
* A location to place the chip revision information.
******************************************************************************/
void SYSTEM_ChipRevisionGet(SYSTEM_ChipRevision_TypeDef *rev)
{
#if defined(_SYSCFG_CHIPREV_FAMILY_MASK) || defined(_SYSCFG_CHIPREV_PARTNUMBER_MASK)
/* On series-2 (and higher) the revision info is in the SYSCFG->CHIPREV register. */
uint32_t chiprev = SYSCFG_readChipRev();
#if defined(_SYSCFG_CHIPREV_PARTNUMBER_MASK)
rev->partNumber = ((chiprev & SYSCFG_CHIPREV_PARTNUMBER1) >> 5) | (chiprev & SYSCFG_CHIPREV_PARTNUMBER0);
#else
rev->family = (chiprev & _SYSCFG_CHIPREV_FAMILY_MASK) >> _SYSCFG_CHIPREV_FAMILY_SHIFT;
#endif
rev->major = (chiprev & _SYSCFG_CHIPREV_MAJOR_MASK) >> _SYSCFG_CHIPREV_MAJOR_SHIFT;
rev->minor = (chiprev & _SYSCFG_CHIPREV_MINOR_MASK) >> _SYSCFG_CHIPREV_MINOR_SHIFT;
#else
uint8_t tmp;
EFM_ASSERT(rev);
/* CHIP FAMILY bit [5:2] */
tmp = (uint8_t)(((ROMTABLE->PID1 & _ROMTABLE_PID1_FAMILYMSB_MASK)
>> _ROMTABLE_PID1_FAMILYMSB_SHIFT) << 2);
/* CHIP FAMILY bit [1:0] */
tmp |= (uint8_t)((ROMTABLE->PID0 & _ROMTABLE_PID0_FAMILYLSB_MASK)
>> _ROMTABLE_PID0_FAMILYLSB_SHIFT);
rev->family = tmp;
/* CHIP MAJOR bit [3:0] */
rev->major = (uint8_t)((ROMTABLE->PID0 & _ROMTABLE_PID0_REVMAJOR_MASK)
>> _ROMTABLE_PID0_REVMAJOR_SHIFT);
/* CHIP MINOR bit [7:4] */
tmp = (uint8_t)(((ROMTABLE->PID2 & _ROMTABLE_PID2_REVMINORMSB_MASK)
>> _ROMTABLE_PID2_REVMINORMSB_SHIFT) << 4);
/* CHIP MINOR bit [3:0] */
tmp |= (uint8_t)((ROMTABLE->PID3 & _ROMTABLE_PID3_REVMINORLSB_MASK)
>> _ROMTABLE_PID3_REVMINORLSB_SHIFT);
rev->minor = tmp;
#endif
}
/***************************************************************************//**
* @brief
* Get a factory calibration value for a given peripheral register.
*
* @param[in] regAddress
* The peripheral calibration register address to get a calibration value for. If
* the calibration value is found, this register is updated with the
* calibration value.
*
* @return
* True if a calibration value exists, false otherwise.
******************************************************************************/
bool SYSTEM_GetCalibrationValue(volatile uint32_t *regAddress)
{
SYSTEM_CalAddrVal_TypeDef * p, * end;
uint32_t s_regAddress = (uint32_t)regAddress;
s_regAddress = s_regAddress & CONVERT_NS_TO_S;
#if defined(MSC_FLASH_CHIPCONFIG_MEM_BASE)
p = (SYSTEM_CalAddrVal_TypeDef *)MSC_FLASH_CHIPCONFIG_MEM_BASE;
end = (SYSTEM_CalAddrVal_TypeDef *)MSC_FLASH_CHIPCONFIG_MEM_END;
#else
p = (SYSTEM_CalAddrVal_TypeDef *)(DEVINFO_BASE & 0xFFFFF000U);
end = (SYSTEM_CalAddrVal_TypeDef *)DEVINFO_BASE;
#endif
for (; p < end; p++) {
if (p->address == 0) {
/* p->address == 0 marks the end of the table */
return false;
}
if (p->address == s_regAddress) {
*regAddress = p->calValue;
return true;
}
}
/* Nothing found for regAddress. */
return false;
}
/***************************************************************************//**
* @brief
* Get family security capability.
*
* @note
* This function retrieves the family security capability based on the
* device number. The device number is one letter and 3 digits:
* DEVICENUMBER = (alpha-'A')*1000 + numeric. i.e. 0d = "A000"; 1123d = "B123".
* The security capabilities are represented by ::SYSTEM_SecurityCapability_TypeDef.
*
* @return
* Security capability of MCU.
******************************************************************************/
SYSTEM_SecurityCapability_TypeDef SYSTEM_GetSecurityCapability(void)
{
SYSTEM_SecurityCapability_TypeDef sc;
#if (_SILICON_LABS_32B_SERIES == 0)
sc = securityCapabilityNA;
#elif (_SILICON_LABS_32B_SERIES == 1)
sc = securityCapabilityBasic;
#else
sc = securityCapabilityUnknown;
#endif
#if (_SILICON_LABS_32B_SERIES == 2)
uint16_t mcuFeatureSetMajor;
uint16_t deviceNumber;
deviceNumber = SYSTEM_GetPartNumber();
mcuFeatureSetMajor = 'A' + (deviceNumber / 1000);
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2)
// override feature set since BRD4182A Rev A00 -> rev B02 are marked "A"
mcuFeatureSetMajor = 'C';
#endif
switch (mcuFeatureSetMajor) {
case 'A':
sc = securityCapabilitySE;
break;
case 'B':
sc = securityCapabilityVault;
break;
case 'C':
sc = securityCapabilityRoT;
break;
default:
sc = securityCapabilityUnknown;
break;
}
#endif
return sc;
}
/***************************************************************************//**
* @brief
* Get the unique number for this device.
*
* @return
* Unique number for this device.
******************************************************************************/
uint64_t SYSTEM_GetUnique(void)
{
#if defined (_DEVINFO_EUI64H_MASK)
uint32_t tmp = DEVINFO->EUI64L;
return (uint64_t)((uint64_t)DEVINFO->EUI64H << 32) | tmp;
#elif defined(_DEVINFO_UNIQUEH_MASK)
uint32_t tmp = DEVINFO->UNIQUEL;
return (uint64_t)((uint64_t)DEVINFO->UNIQUEH << 32) | tmp;
#else
#error (em_system.c): Location of device unique number is not defined.
#endif
}
/***************************************************************************//**
* @brief
* Get the production revision for this part.
*
* @return
* Production revision for this part.
******************************************************************************/
uint8_t SYSTEM_GetProdRev(void)
{
#if defined (_DEVINFO_PART_PROD_REV_MASK)
return (uint8_t)((DEVINFO->PART & _DEVINFO_PART_PROD_REV_MASK)
>> _DEVINFO_PART_PROD_REV_SHIFT);
#elif defined (_DEVINFO_INFO_PRODREV_MASK)
return (uint8_t)((DEVINFO->INFO & _DEVINFO_INFO_PRODREV_MASK)
>> _DEVINFO_INFO_PRODREV_SHIFT);
#else
#error (em_system.c): Location of production revision is not defined.
#endif
}
/***************************************************************************//**
* @brief
* Get the SRAM Base Address.
*
* @note
* This function is used to retrieve the base address of the SRAM.
*
* @return
* Base address SRAM (32-bit unsigned integer).
******************************************************************************/
uint32_t SYSTEM_GetSRAMBaseAddress(void)
{
return (uint32_t)SRAM_BASE;
}
/***************************************************************************//**
* @brief
* Get the SRAM size (in KB).
*
* @note
* This function retrieves SRAM size by reading the chip device
* info structure. If your binary is made for one specific device only,
* use SRAM_SIZE instead.
*
* @return
* Size of internal SRAM (in KB).
******************************************************************************/
uint16_t SYSTEM_GetSRAMSize(void)
{
uint16_t sizekb;
#if defined(_EFM32_GECKO_FAMILY)
/* Early Gecko devices had a bug where SRAM and Flash size were swapped. */
if (SYSTEM_GetProdRev() < 5) {
sizekb = (DEVINFO->MSIZE & _DEVINFO_MSIZE_FLASH_MASK)
>> _DEVINFO_MSIZE_FLASH_SHIFT;
}
#endif
sizekb = (uint16_t)((DEVINFO->MSIZE & _DEVINFO_MSIZE_SRAM_MASK)
>> _DEVINFO_MSIZE_SRAM_SHIFT);
#if defined(_SILICON_LABS_GECKO_INTERNAL_SDID_80) && defined(_EFR_DEVICE)
/* Do not include EFR32xG1 RAMH. */
sizekb--;
#endif
return sizekb;
}
/***************************************************************************//**
* @brief
* Get the flash size (in KB).
*
* @note
* This function retrieves flash size by reading the chip device
* info structure. If your binary is made for one specific device only,
* use FLASH_SIZE instead.
*
* @return
* Size of internal flash (in KB).
******************************************************************************/
uint16_t SYSTEM_GetFlashSize(void)
{
#if defined(_EFM32_GECKO_FAMILY)
/* Early Gecko devices had a bug where SRAM and Flash size were swapped. */
if (SYSTEM_GetProdRev() < 5) {
return (DEVINFO->MSIZE & _DEVINFO_MSIZE_SRAM_MASK)
>> _DEVINFO_MSIZE_SRAM_SHIFT;
}
#endif
return (uint16_t)((DEVINFO->MSIZE & _DEVINFO_MSIZE_FLASH_MASK)
>> _DEVINFO_MSIZE_FLASH_SHIFT);
}
/***************************************************************************//**
* @brief
* Get the flash page size in bytes.
*
* @note
* This function retrieves flash page size by reading the chip device
* info structure. If your binary is made for one specific device only,
* use FLASH_PAGE_SIZE instead.
*
* @return
* Page size of internal flash in bytes.
******************************************************************************/
uint32_t SYSTEM_GetFlashPageSize(void)
{
uint32_t tmp;
#if defined(_SILICON_LABS_32B_SERIES_0)
#if defined(_EFM32_GIANT_FAMILY)
if (SYSTEM_GetProdRev() < 18) {
/* Early Giant/Leopard devices did not have MEMINFO in DEVINFO. */
return FLASH_PAGE_SIZE;
}
#elif defined(_EFM32_ZERO_FAMILY)
if (SYSTEM_GetProdRev() < 24) {
/* Early Zero devices have an incorrect DEVINFO flash page size */
return FLASH_PAGE_SIZE;
}
#endif
#endif
#if defined(_DEVINFO_MEMINFO_FLASHPAGESIZE_MASK)
tmp = (DEVINFO->MEMINFO & _DEVINFO_MEMINFO_FLASHPAGESIZE_MASK)
>> _DEVINFO_MEMINFO_FLASHPAGESIZE_SHIFT;
#elif defined(_DEVINFO_MEMINFO_FLASH_PAGE_SIZE_MASK)
tmp = (DEVINFO->MEMINFO & _DEVINFO_MEMINFO_FLASH_PAGE_SIZE_MASK)
>> _DEVINFO_MEMINFO_FLASH_PAGE_SIZE_SHIFT;
#else
#error (em_system.c): Location of flash page size is not defined.
#endif
return 1UL << ((tmp + 10UL) & 0x1FUL);
}
/***************************************************************************//**
* @brief
* Get the MCU part number.
*
* @return
* The part number of MCU.
******************************************************************************/
uint16_t SYSTEM_GetPartNumber(void)
{
#if defined(_DEVINFO_PART_DEVICENUM_MASK)
return (uint16_t)((DEVINFO->PART & _DEVINFO_PART_DEVICENUM_MASK)
>> _DEVINFO_PART_DEVICENUM_SHIFT);
#elif defined(_DEVINFO_PART_DEVICE_NUMBER_MASK)
return (uint16_t)((DEVINFO->PART & _DEVINFO_PART_DEVICE_NUMBER_MASK)
>> _DEVINFO_PART_DEVICE_NUMBER_SHIFT);
#else
#error (em_system.c): Location of device part number is not defined.
#endif
}
/***************************************************************************//**
* @brief
* Get the calibration temperature (in degrees Celsius).
*
* @return
* Calibration temperature in Celsius.
******************************************************************************/
uint8_t SYSTEM_GetCalibrationTemperature(void)
{
#if defined(_DEVINFO_CAL_TEMP_MASK)
return (uint8_t)((DEVINFO->CAL & _DEVINFO_CAL_TEMP_MASK)
>> _DEVINFO_CAL_TEMP_SHIFT);
#elif defined(_DEVINFO_CALTEMP_TEMP_MASK)
return (uint8_t)((DEVINFO->CALTEMP & _DEVINFO_CALTEMP_TEMP_MASK)
>> _DEVINFO_CALTEMP_TEMP_SHIFT);
#else
#error (em_system.c): Location of calibration temperature is not defined.
#endif
}
/***************************************************************************//**
* @brief
* Get the MCU family identifier.
*
* @note
* This function retrieves family ID by reading the chip's device info
* structure in flash memory. Users can retrieve family ID directly
* by reading DEVINFO->PART item and decode with mask and shift
* \#defines defined in \<part_family\>_devinfo.h (refer to code
* below for details).
*
* @return
* Family identifier of MCU.
******************************************************************************/
SYSTEM_PartFamily_TypeDef SYSTEM_GetFamily(void)
{
#if defined(_DEVINFO_PART_FAMILY_MASK)
return (SYSTEM_PartFamily_TypeDef)
((uint32_t)((DEVINFO->PART & (_DEVINFO_PART_FAMILY_MASK
| _DEVINFO_PART_FAMILYNUM_MASK))));
#elif defined(_DEVINFO_PART_DEVICE_FAMILY_MASK)
return (SYSTEM_PartFamily_TypeDef)
((uint32_t)((DEVINFO->PART & _DEVINFO_PART_DEVICE_FAMILY_MASK)
>> _DEVINFO_PART_DEVICE_FAMILY_SHIFT));
#else
#error (em_system.h): Location of device family name is not defined.
#endif
}
/** @} (end addtogroup system) */

View File

@@ -0,0 +1,520 @@
/***************************************************************************//**
* @file
* @brief Timer/counter (TIMER) Peripheral API
*******************************************************************************
* # 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 "sl_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(TIMER9)
#define TIMER_DEVICE_ID(timer) ( \
(timer) == TIMER0 ? 0 \
: (timer) == TIMER1 ? 1 \
: (timer) == TIMER2 ? 2 \
: (timer) == TIMER3 ? 3 \
: (timer) == TIMER4 ? 4 \
: (timer) == TIMER5 ? 5 \
: (timer) == TIMER6 ? 6 \
: (timer) == TIMER7 ? 7 \
: (timer) == TIMER8 ? 8 \
: (timer) == TIMER9 ? 9 \
: -1)
#elif defined(TIMER7)
#define TIMER_DEVICE_ID(timer) ( \
(timer) == TIMER0 ? 0 \
: (timer) == TIMER1 ? 1 \
: (timer) == TIMER2 ? 2 \
: (timer) == TIMER3 ? 3 \
: (timer) == TIMER4 ? 4 \
: (timer) == TIMER5 ? 5 \
: (timer) == TIMER6 ? 6 \
: (timer) == TIMER7 ? 7 \
: -1)
#elif 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);
volatile PRS_TIMERn_TypeDef * base = (PRS_TIMERn_TypeDef *) &PRS->CONSUMER_TIMER0_CC0;
EFM_ASSERT(i >= 0);
if (i >= 0) {
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->disSyncOut ? TIMER_CFG_DISSYNCOUT : 0)
| (init->ati ? TIMER_CFG_ATI : 0)
| (init->rssCoist ? TIMER_CFG_RSSCOIST : 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_0) || defined(_SILICON_LABS_32B_SERIES_1)
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)
#if defined(TIMER_CTRL_DISSYNCOUT)
| (init->disSyncOut ? TIMER_CTRL_DISSYNCOUT : 0)
#endif
| (init->sync ? TIMER_CTRL_SYNC : 0);
#if defined(TIMER_CTRL_X2CNT) && defined(TIMER_CTRL_ATI) && defined(TIMER_CTRL_RSSCOIST)
ctrlRegVal |= (init->count2x ? TIMER_CTRL_X2CNT : 0)
| (init->ati ? TIMER_CTRL_ATI : 0)
| (init->rssCoist ? TIMER_CTRL_RSSCOIST : 0);
#endif
#else
ctrlRegVal = ((uint32_t)init->fallAction << _TIMER_CTRL_FALLA_SHIFT)
| ((uint32_t)init->riseAction << _TIMER_CTRL_RISEA_SHIFT)
| (init->count2x ? TIMER_CTRL_X2CNT : 0);
#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_REF_CH_VALIDATE(timer, ch));
#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. */
for (i = 0; TIMER_REF_CH_VALIDATE(timer, 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;
#if defined(_TIMER_EN_DISABLING_MASK)
while (timer->EN & _TIMER_EN_DISABLING_MASK) {
}
#endif
timer->CFG = _TIMER_CFG_RESETVALUE;
for (i = 0; TIMER_REF_CH_VALIDATE(timer, 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) */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,636 @@
/***************************************************************************//**
* @file
* @brief Digital to Analog Converter (VDAC) Peripheral API
*******************************************************************************
* # 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_vdac.h"
#if defined(VDAC_COUNT) && (VDAC_COUNT > 0)
#include "em_cmu.h"
/***************************************************************************//**
* @addtogroup vdac
* @{
******************************************************************************/
/*******************************************************************************
******************************* DEFINES ***********************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
/** Validation of the VDAC channel for assert statements. */
#define VDAC_CH_VALID(ch) ((ch) <= 1)
/** A maximum VDAC clock. */
#define VDAC_MAX_CLOCK 1000000
/** The maximum clock frequency of the internal clock oscillator, 10 MHz + 20%. */
#define VDAC_INTERNAL_CLOCK_FREQ 12000000
/** @endcond */
/*******************************************************************************
*************************** LOCAL FUNCTIONS *******************************
******************************************************************************/
/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
#if defined(_VDAC_EN_MASK)
static void VDAC_DisableModule(VDAC_TypeDef* vdac)
{
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
/* Wait for all synchronizations to finish */
if (vdac->EN & VDAC_EN_EN) {
vdac->CMD = _VDAC_CMD_CH0DIS_MASK;
while (vdac->STATUS & (VDAC_STATUS_CH0ENS)) {
}
vdac->CMD = _VDAC_CMD_CH1DIS_MASK;
while (vdac->STATUS & (VDAC_STATUS_CH1ENS)) {
}
#if defined(_VDAC_CMD_CH0FIFOFLUSH_MASK)
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
vdac->CMD = VDAC_CMD_CH0FIFOFLUSH | VDAC_CMD_CH1FIFOFLUSH;
while (vdac->STATUS & (VDAC_STATUS_SYNCBUSY | VDAC_STATUS_CH0FIFOFLBUSY | VDAC_STATUS_CH1FIFOFLBUSY)) {
}
#endif
vdac->EN_CLR = _VDAC_EN_EN_MASK;
while (vdac->EN & _VDAC_EN_DISABLING_MASK) {
}
}
}
#endif
/** @endcond */
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Enable/disable the VDAC channel.
*
* @param[in] vdac
* A pointer to the VDAC peripheral register block.
*
* @param[in] ch
* A channel to enable/disable.
*
* @param[in] enable
* True to enable VDAC channel, false to disable.
******************************************************************************/
void VDAC_Enable(VDAC_TypeDef *vdac, unsigned int ch, bool enable)
{
EFM_ASSERT(VDAC_REF_VALID(vdac));
EFM_ASSERT(VDAC_CH_VALID(ch));
#if defined(_VDAC_STATUS_SYNCBUSY_MASK)
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
#endif
if (ch == 0) {
if (enable) {
vdac->CMD = VDAC_CMD_CH0EN;
while ((vdac->STATUS & VDAC_STATUS_CH0ENS) == 0) {
}
} else {
vdac->CMD = VDAC_CMD_CH0DIS;
while (vdac->STATUS & VDAC_STATUS_CH0ENS) {
}
#if defined(_VDAC_CMD_CH0FIFOFLUSH_MASK)
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
vdac->CMD = VDAC_CMD_CH0FIFOFLUSH;
while (vdac->STATUS & VDAC_STATUS_CH0FIFOFLBUSY) {
}
#endif
}
} else {
if (enable) {
vdac->CMD = VDAC_CMD_CH1EN;
while ((vdac->STATUS & VDAC_STATUS_CH1ENS) == 0) {
}
} else {
vdac->CMD = VDAC_CMD_CH1DIS;
while (vdac->STATUS & VDAC_STATUS_CH1ENS) {
}
#if defined(_VDAC_CMD_CH1FIFOFLUSH_MASK)
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
vdac->CMD = VDAC_CMD_CH1FIFOFLUSH;
while (vdac->STATUS & VDAC_STATUS_CH1FIFOFLBUSY) {
}
#endif
}
}
}
/***************************************************************************//**
* @brief
* Initialize VDAC.
*
* @details
* Initializes the common parts for both channels. This function will also load
* calibration values from the Device Information (DI) page into the VDAC
* calibration register.
* To complete a VDAC setup, channel control configuration must also be done.
* See VDAC_InitChannel().
*
* @note
* This function will disable both channels prior to configuration.
*
* @param[in] vdac
* A pointer to the VDAC peripheral register block.
*
* @param[in] init
* A pointer to the VDAC initialization structure.
******************************************************************************/
void VDAC_Init(VDAC_TypeDef *vdac, const VDAC_Init_TypeDef *init)
{
EFM_ASSERT(VDAC_REF_VALID(vdac));
uint32_t config = 0;
#if defined(_SILICON_LABS_32B_SERIES_0) || defined(_SILICON_LABS_32B_SERIES_1)
uint32_t cal;
uint32_t const volatile *calData;
/* Make sure both channels are disabled. */
vdac->CMD = VDAC_CMD_CH0DIS | VDAC_CMD_CH1DIS;
while (vdac->STATUS & (VDAC_STATUS_CH0ENS | VDAC_STATUS_CH1ENS)) {
}
/* Get the OFFSETTRIM calibration value. */
cal = ((DEVINFO->VDAC0CH1CAL & _DEVINFO_VDAC0CH1CAL_OFFSETTRIM_MASK)
>> _DEVINFO_VDAC0CH1CAL_OFFSETTRIM_SHIFT)
<< _VDAC_CAL_OFFSETTRIM_SHIFT;
if (init->mainCalibration) {
calData = &DEVINFO->VDAC0MAINCAL;
} else {
calData = &DEVINFO->VDAC0ALTCAL;
}
/* Get the correct GAINERRTRIM calibration value. */
switch (init->reference) {
case vdacRef1V25Ln:
config = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25LN_MASK)
>> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25LN_SHIFT;
break;
case vdacRef2V5Ln:
config = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5LN_MASK)
>> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5LN_SHIFT;
break;
case vdacRef1V25:
config = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25_MASK)
>> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM1V25_SHIFT;
break;
case vdacRef2V5:
config = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5_MASK)
>> _DEVINFO_VDAC0MAINCAL_GAINERRTRIM2V5_SHIFT;
break;
case vdacRefAvdd:
case vdacRefExtPin:
config = (*calData & _DEVINFO_VDAC0MAINCAL_GAINERRTRIMVDDANAEXTPIN_MASK)
>> _DEVINFO_VDAC0MAINCAL_GAINERRTRIMVDDANAEXTPIN_SHIFT;
break;
}
/* Set the sGAINERRTRIM calibration value. */
cal |= config << _VDAC_CAL_GAINERRTRIM_SHIFT;
/* Get the GAINERRTRIMCH1 calibration value. */
switch (init->reference) {
case vdacRef1V25Ln:
case vdacRef1V25:
case vdacRefAvdd:
case vdacRefExtPin:
config = (DEVINFO->VDAC0CH1CAL & _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1A_MASK)
>> _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1A_SHIFT;
break;
case vdacRef2V5Ln:
case vdacRef2V5:
config = (DEVINFO->VDAC0CH1CAL & _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1B_MASK)
>> _DEVINFO_VDAC0CH1CAL_GAINERRTRIMCH1B_SHIFT;
break;
}
/* Set the GAINERRTRIM calibration value. */
cal |= config << _VDAC_CAL_GAINERRTRIMCH1_SHIFT;
config = ((uint32_t)init->asyncClockMode << _VDAC_CTRL_DACCLKMODE_SHIFT)
| ((uint32_t)init->warmupKeepOn << _VDAC_CTRL_WARMUPMODE_SHIFT)
| ((uint32_t)init->refresh << _VDAC_CTRL_REFRESHPERIOD_SHIFT)
| (((uint32_t)init->prescaler << _VDAC_CTRL_PRESC_SHIFT)
& _VDAC_CTRL_PRESC_MASK)
| ((uint32_t)init->reference << _VDAC_CTRL_REFSEL_SHIFT)
| ((uint32_t)init->ch0ResetPre << _VDAC_CTRL_CH0PRESCRST_SHIFT)
| ((uint32_t)init->outEnablePRS << _VDAC_CTRL_OUTENPRS_SHIFT)
| ((uint32_t)init->sineEnable << _VDAC_CTRL_SINEMODE_SHIFT)
| ((uint32_t)init->diff << _VDAC_CTRL_DIFF_SHIFT);
/* Write to VDAC registers. */
vdac->CAL = cal;
vdac->CTRL = config;
#elif defined(_SILICON_LABS_32B_SERIES_2)
VDAC_DisableModule(vdac);
config = (
#if defined(VDAC_CFG_SINEMODEPRS)
((uint32_t)init->sineModePrsEnable ? VDAC_CFG_SINEMODEPRS : 0U) |
#endif
#if defined(VDAC_CFG_OUTENPRS)
((uint32_t)init->prsOutEnable ? VDAC_CFG_OUTENPRS : 0U) |
#endif
(((uint32_t)init->warmupTime << _VDAC_CFG_WARMUPTIME_SHIFT) & _VDAC_CFG_WARMUPTIME_MASK)
| ((uint32_t)init->dbgHalt << _VDAC_CFG_DBGHALT_SHIFT)
| ((uint32_t)init->onDemandClk << _VDAC_CFG_ONDEMANDCLK_SHIFT)
| ((uint32_t)init->dmaWakeUp << _VDAC_CFG_DMAWU_SHIFT)
| ((uint32_t)init->biasKeepWarm << _VDAC_CFG_BIASKEEPWARM_SHIFT)
| ((uint32_t)init->refresh << _VDAC_CFG_REFRESHPERIOD_SHIFT)
| ((uint32_t)init->timerOverflow << _VDAC_CFG_TIMEROVRFLOWPERIOD_SHIFT)
| (((uint32_t)init->prescaler << _VDAC_CFG_PRESC_SHIFT) & _VDAC_CFG_PRESC_MASK)
| ((uint32_t)init->reference << _VDAC_CFG_REFRSEL_SHIFT)
| ((uint32_t)init->ch0ResetPre << _VDAC_CFG_CH0PRESCRST_SHIFT)
| ((uint32_t)init->sineReset << _VDAC_CFG_SINERESET_SHIFT)
| ((uint32_t)init->sineEnable << _VDAC_CFG_SINEMODE_SHIFT)
| ((uint32_t)init->diff << _VDAC_CFG_DIFF_SHIFT));
vdac->CFG = config;
#endif
}
/***************************************************************************//**
* @brief
* Initialize a VDAC channel.
*
* @param[in] vdac
* A pointer to the VDAC peripheral register block.
*
* @param[in] init
* A pointer to the VDAC channel initialization structure.
*
* @param[in] ch
* A channel number to initialize.
******************************************************************************/
void VDAC_InitChannel(VDAC_TypeDef *vdac,
const VDAC_InitChannel_TypeDef *init,
unsigned int ch)
{
uint32_t channelConfig, vdacStatus;
EFM_ASSERT(VDAC_REF_VALID(vdac));
EFM_ASSERT(VDAC_CH_VALID(ch));
vdacStatus = vdac->STATUS;
#if defined(_SILICON_LABS_32B_SERIES_0) || defined(_SILICON_LABS_32B_SERIES_1)
/* Make sure both channels are disabled. */
vdac->CMD = VDAC_CMD_CH0DIS | VDAC_CMD_CH1DIS;
while (vdac->STATUS & (VDAC_STATUS_CH0ENS | VDAC_STATUS_CH1ENS)) {
}
channelConfig = ((uint32_t)init->prsSel << _VDAC_CH0CTRL_PRSSEL_SHIFT)
| ((uint32_t)init->prsAsync << _VDAC_CH0CTRL_PRSASYNC_SHIFT)
| ((uint32_t)init->trigMode << _VDAC_CH0CTRL_TRIGMODE_SHIFT)
| ((uint32_t)init->sampleOffMode << _VDAC_CH0CTRL_CONVMODE_SHIFT);
if (ch == 0) {
vdac->CH0CTRL = channelConfig;
} else {
vdac->CH1CTRL = channelConfig;
}
#elif defined(_SILICON_LABS_32B_SERIES_2)
VDAC_DisableModule(vdac);
channelConfig = ((uint32_t)init->warmupKeepOn << _VDAC_CH0CFG_KEEPWARM_SHIFT)
| ((uint32_t)init->highCapLoadEnable << _VDAC_CH0CFG_HIGHCAPLOADEN_SHIFT)
| (((uint32_t)init->fifoLowDataThreshold << _VDAC_CH0CFG_FIFODVL_SHIFT) & _VDAC_CH0CFG_FIFODVL_MASK)
| ((uint32_t)init->chRefreshSource << _VDAC_CH0CFG_REFRESHSOURCE_SHIFT)
| ((uint32_t)init->trigMode << _VDAC_CH0CFG_TRIGMODE_SHIFT)
| ((uint32_t)init->powerMode << _VDAC_CH0CFG_POWERMODE_SHIFT)
| ((uint32_t)init->sampleOffMode << _VDAC_CH0CFG_CONVMODE_SHIFT);
if (ch == 0) {
vdac->CH0CFG = channelConfig;
vdac->OUTTIMERCFG = ((uint32_t)(vdac->OUTTIMERCFG & ~(_VDAC_OUTTIMERCFG_CH0OUTHOLDTIME_MASK)))
| (((uint32_t)init->holdOutTime << _VDAC_OUTTIMERCFG_CH0OUTHOLDTIME_SHIFT) & _VDAC_OUTTIMERCFG_CH0OUTHOLDTIME_MASK);
vdac->EN_SET = _VDAC_EN_EN_MASK;
vdac->OUTCTRL = ((uint32_t)(vdac->OUTCTRL & ~(_VDAC_OUTCTRL_ABUSPINSELCH0_MASK | _VDAC_OUTCTRL_ABUSPORTSELCH0_MASK | _VDAC_OUTCTRL_SHORTCH0_MASK | _VDAC_OUTCTRL_AUXOUTENCH0_MASK | _VDAC_OUTCTRL_MAINOUTENCH0_MASK)))
| (((uint32_t)init->pin << _VDAC_OUTCTRL_ABUSPINSELCH0_SHIFT) & _VDAC_OUTCTRL_ABUSPINSELCH0_MASK)
| ((uint32_t)init->port << _VDAC_OUTCTRL_ABUSPORTSELCH0_SHIFT)
| ((uint32_t)init->shortOutput << _VDAC_OUTCTRL_SHORTCH0_SHIFT)
| ((uint32_t)init->auxOutEnable << _VDAC_OUTCTRL_AUXOUTENCH0_SHIFT)
| ((uint32_t)init->mainOutEnable << _VDAC_OUTCTRL_MAINOUTENCH0_SHIFT);
} else if (ch == 1) {
vdac->CH1CFG = channelConfig;
vdac->OUTTIMERCFG = (vdac->OUTTIMERCFG & ~(_VDAC_OUTTIMERCFG_CH1OUTHOLDTIME_MASK))
| (((uint32_t)init->holdOutTime << _VDAC_OUTTIMERCFG_CH1OUTHOLDTIME_SHIFT) & _VDAC_OUTTIMERCFG_CH1OUTHOLDTIME_MASK);
vdac->EN_SET = _VDAC_EN_EN_MASK;
vdac->OUTCTRL = ((uint32_t)(vdac->OUTCTRL & ~(_VDAC_OUTCTRL_ABUSPINSELCH1_MASK | _VDAC_OUTCTRL_ABUSPORTSELCH1_MASK | _VDAC_OUTCTRL_SHORTCH1_MASK | _VDAC_OUTCTRL_AUXOUTENCH1_MASK | _VDAC_OUTCTRL_MAINOUTENCH1_MASK)))
| (((uint32_t)init->pin << _VDAC_OUTCTRL_ABUSPINSELCH1_SHIFT) & _VDAC_OUTCTRL_ABUSPINSELCH1_MASK)
| ((uint32_t)init->port << _VDAC_OUTCTRL_ABUSPORTSELCH1_SHIFT)
| ((uint32_t)init->shortOutput << _VDAC_OUTCTRL_SHORTCH1_SHIFT)
| ((uint32_t)init->auxOutEnable << _VDAC_OUTCTRL_AUXOUTENCH1_SHIFT)
| ((uint32_t)init->mainOutEnable << _VDAC_OUTCTRL_MAINOUTENCH1_SHIFT);
}
#endif
/* Check if the channel must be enabled. */
if (init->enable) {
if (ch == 0) {
vdac->CMD = VDAC_CMD_CH0EN;
} else {
vdac->CMD = VDAC_CMD_CH1EN;
}
}
/* Check if the other channel had to be turned off above
* and needs to be turned on again. */
if (ch == 0) {
if (vdacStatus & VDAC_STATUS_CH1ENS) {
vdac->CMD = VDAC_CMD_CH1EN;
}
} else {
if (vdacStatus & VDAC_STATUS_CH0ENS) {
vdac->CMD = VDAC_CMD_CH0EN;
}
}
}
/***************************************************************************//**
* @brief
* Set the output signal of a VDAC channel to a given value.
*
* @details
* This function sets the output signal of a VDAC channel by writing @p value
* to the corresponding CHnDATA register.
*
* @param[in] vdac
* A pointer to the VDAC peripheral register block.
*
* @param[in] channel
* A channel number to set the output of.
*
* @param[in] value
* A value to write to the channel output register CHnDATA.
******************************************************************************/
void VDAC_ChannelOutputSet(VDAC_TypeDef *vdac,
unsigned int channel,
uint32_t value)
{
switch (channel) {
case 0:
VDAC_Channel0OutputSet(vdac, value);
break;
case 1:
VDAC_Channel1OutputSet(vdac, value);
break;
default:
EFM_ASSERT(0);
break;
}
}
#if defined(_SILICON_LABS_32B_SERIES_0) || defined(_SILICON_LABS_32B_SERIES_1)
/***************************************************************************//**
* @brief
* Calculate the prescaler value used to determine VDAC clock.
*
* @details
* The VDAC clock is given by the input clock divided by the prescaler+1.
*
* VDAC_CLK = IN_CLK / (prescale + 1)
*
* The maximum VDAC clock is 1 MHz. The input clock is HFPERCLK/HFPERCCLK
* when VDAC synchronous mode is selected, or an internal oscillator of
* 10 MHz +/- 20% when asynchronous mode is selected.
*
* @note
* If the requested VDAC frequency is low and the maximum prescaler value can't
* adjust the actual VDAC frequency lower than requested, the maximum prescaler
* value is returned resulting in a higher VDAC frequency than requested.
*
* @param[in] vdacFreq VDAC frequency target. The frequency will automatically
* be adjusted to be below maximum allowed VDAC clock.
*
* @param[in] syncMode Set to true if you intend to use VDAC in synchronous
* mode.
*
* @param[in] hfperFreq Frequency in Hz of HFPERCLK/HFPERCCLK oscillator.
* Set to 0 to use the currently defined HFPERCLK/HFPERCCLK clock setting.
* This parameter is only used when syncMode is set to true.
*
* @return
* A prescaler value to use for VDAC to achieve a clock value less than
* or equal to @p vdacFreq.
******************************************************************************/
uint32_t VDAC_PrescaleCalc(uint32_t vdacFreq, bool syncMode, uint32_t hfperFreq)
{
uint32_t ret, refFreq;
/* Make sure that the selected VDAC clock is below the maximum value. */
if (vdacFreq > VDAC_MAX_CLOCK) {
vdacFreq = VDAC_MAX_CLOCK;
}
if (!syncMode) {
refFreq = VDAC_INTERNAL_CLOCK_FREQ;
} else {
if (hfperFreq) {
refFreq = hfperFreq;
} else {
refFreq = CMU_ClockFreqGet(cmuClock_VDAC0);
}
}
/* Iterate to determine the best prescaler value. Start with the lowest */
/* prescaler value to get the first equal or less VDAC */
/* frequency value. */
for (ret = 0; ret <= (_VDAC_CTRL_PRESC_MASK >> _VDAC_CTRL_PRESC_SHIFT); ret++) {
if ((refFreq / (ret + 1)) <= vdacFreq) {
break;
}
}
/* If ret is higher than the maximum prescaler value, make sure to return
the maximum value. */
if (ret > (_VDAC_CTRL_PRESC_MASK >> _VDAC_CTRL_PRESC_SHIFT)) {
ret = _VDAC_CTRL_PRESC_MASK >> _VDAC_CTRL_PRESC_SHIFT;
}
return ret;
}
#else
/***************************************************************************//**
* @brief
* Calculate the prescaler value used to determine VDAC clock.
*
* @details
* The VDAC clock is given by the input clock divided by the prescaler+1.
*
* VDAC_CLK = IN_CLK / (prescale + 1)
*
* The maximum VDAC clock is 1 MHz.
*
* @note
* If the requested VDAC frequency is low and the maximum prescaler value can't
* adjust the actual VDAC frequency lower than requested, the maximum prescaler
* value is returned resulting in a higher VDAC frequency than requested.
*
* @param[in] vdac
* Pointer to VDAC peripheral register block.
*
* @param[in] vdacFreq VDAC frequency target. The frequency will automatically
* be adjusted to be below maximum allowed VDAC clock.
*
* @return
* A prescaler value to use for VDAC to achieve a clock value less than
* or equal to @p vdacFreq.
******************************************************************************/
uint32_t VDAC_PrescaleCalc(VDAC_TypeDef *vdac, uint32_t vdacFreq)
{
uint32_t ret = 0;
uint32_t refFreq = 0;
/* Make sure that the selected VDAC clock is below the maximum value. */
if (vdacFreq > VDAC_MAX_CLOCK) {
vdacFreq = VDAC_MAX_CLOCK;
}
if (vdac == VDAC0) {
refFreq = CMU_ClockFreqGet(cmuClock_VDAC0);
}
#if defined(VDAC1)
else if (vdac == VDAC1) {
refFreq = CMU_ClockFreqGet(cmuClock_VDAC1);
}
#endif
else {
EFM_ASSERT(0);
}
/* Iterate to determine the best prescaler value. Start with the lowest */
/* prescaler value to get the first equal or less VDAC */
/* frequency value. */
for (ret = 0; ret <= (_VDAC_CFG_PRESC_MASK >> _VDAC_CFG_PRESC_SHIFT); ret++) {
if ((refFreq / (ret + 1)) <= vdacFreq) {
break;
}
}
/* If ret is higher than the maximum prescaler value, make sure to return
the maximum value. */
if (ret > (_VDAC_CFG_PRESC_MASK >> _VDAC_CFG_PRESC_SHIFT)) {
ret = _VDAC_CFG_PRESC_MASK >> _VDAC_CFG_PRESC_SHIFT;
}
return ret;
}
#endif
/***************************************************************************//**
* @brief
* Reset VDAC to same state that it was in after a hardwares reset.
*
* @param[in] vdac
* A pointer to the VDAC peripheral register block.
******************************************************************************/
void VDAC_Reset(VDAC_TypeDef *vdac)
{
#if defined(VDAC_SWRST_SWRST)
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
/* Wait for all synchronizations to finish and disable the vdac channels */
if (vdac->EN & VDAC_EN_EN) {
vdac->CMD = _VDAC_CMD_CH0DIS_MASK;
while (vdac->STATUS & VDAC_STATUS_CH0ENS ) {
}
vdac->CMD = _VDAC_CMD_CH1DIS_MASK;
while (vdac->STATUS & VDAC_STATUS_CH1ENS ) {
}
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
vdac->CMD = _VDAC_CMD_CH0FIFOFLUSH_MASK | _VDAC_CMD_CH1FIFOFLUSH_MASK;
while (vdac->STATUS & (VDAC_STATUS_CH0FIFOFLBUSY | VDAC_STATUS_CH1FIFOFLBUSY)) {
}
while (vdac->STATUS & VDAC_STATUS_SYNCBUSY) {
}
}
vdac->SWRST_SET = VDAC_SWRST_SWRST;
while (vdac->SWRST & _VDAC_SWRST_RESETTING_MASK) {
}
#else
/* Disable channels before resetting other registers. */
vdac->CMD = VDAC_CMD_CH0DIS | VDAC_CMD_CH1DIS;
while (vdac->STATUS & (VDAC_STATUS_CH0ENS | VDAC_STATUS_CH1ENS)) {
}
vdac->CH0CTRL = _VDAC_CH0CTRL_RESETVALUE;
vdac->CH1CTRL = _VDAC_CH1CTRL_RESETVALUE;
vdac->CH0DATA = _VDAC_CH0DATA_RESETVALUE;
vdac->CH1DATA = _VDAC_CH1DATA_RESETVALUE;
vdac->CTRL = _VDAC_CTRL_RESETVALUE;
vdac->IEN = _VDAC_IEN_RESETVALUE;
vdac->IFC = _VDAC_IFC_MASK;
vdac->CAL = _VDAC_CAL_RESETVALUE;
#endif
}
/** @} (end addtogroup vdac) */
#endif /* defined(VDAC_COUNT) && (VDAC_COUNT > 0) */

View File

@@ -0,0 +1,355 @@
/***************************************************************************//**
* @file
* @brief Watchdog (WDOG) peripheral API
*******************************************************************************
* # 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_wdog.h"
#if defined(WDOG_COUNT) && (WDOG_COUNT > 0)
#include "em_bus.h"
#include "em_core.h"
/***************************************************************************//**
* @addtogroup wdog WDOG - Watchdog
* @brief Watchdog (WDOG) Peripheral API
* @details
* This module contains functions to control the WDOG peripheral of Silicon
* Labs 32-bit MCUs and SoCs. The WDOG resets the system in case of a fault
* condition.
* @{
******************************************************************************/
/** In some scenarioes when the watchdog is disabled the synchronization
* register might be set and not be cleared until the watchdog is enabled
* again. This will happen when for instance some watchdog register is modified
* while the watchdog clock is disabled. In these scenarioes we need to make
* sure that the software does not wait forever. */
#define WDOG_SYNC_TIMEOUT 30000
/*******************************************************************************
************************** GLOBAL FUNCTIONS *******************************
******************************************************************************/
/***************************************************************************//**
* @brief
* Enable/disable the watchdog timer.
*
* @note
* This function modifies the WDOG CTRL register which requires
* synchronization into the low-frequency domain. If this register is modified
* before a previous update to the same register has completed, this function
* will stall until the previous synchronization has completed.
*
* @param[in] wdog
* A pointer to the WDOG peripheral register block.
*
* @param[in] enable
* True to enable Watchdog, false to disable. Watchdog cannot be disabled if
* it's been locked.
******************************************************************************/
void WDOGn_Enable(WDOG_TypeDef *wdog, bool enable)
{
// SYNCBUSY may stall when locked.
#if defined(_WDOG_STATUS_MASK)
if ((wdog->STATUS & _WDOG_STATUS_LOCK_MASK) == WDOG_STATUS_LOCK_LOCKED) {
return;
}
#else
if (wdog->CTRL & WDOG_CTRL_LOCK) {
return;
}
#endif
#if defined(_WDOG_EN_MASK)
if (!enable) {
while (wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) {
}
wdog->EN_CLR = WDOG_EN_EN;
#if defined(_WDOG_EN_DISABLING_MASK)
while (wdog->EN & _WDOG_EN_DISABLING_MASK) {
}
#endif
} else {
wdog->EN_SET = WDOG_EN_EN;
}
#else
// Wait for previous operations/modifications to complete
int i = 0;
while (((wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U)
&& (i < WDOG_SYNC_TIMEOUT)) {
i++;
}
bool wdogState = ((wdog->CTRL & _WDOG_CTRL_EN_MASK) != 0U);
// Make sure to only write to the CTRL register if we are changing mode
if (wdogState != enable) {
BUS_RegBitWrite(&wdog->CTRL, _WDOG_CTRL_EN_SHIFT, enable);
}
#endif
}
/***************************************************************************//**
* @brief
* Feed WDOG.
*
* @details
* When WDOG is activated, it must be fed (i.e., clearing the counter)
* before it reaches the defined timeout period. Otherwise, WDOG
* will generate a reset.
*
* @note
* Note that WDOG is an asynchronous peripheral and when calling the
* WDOGn_Feed() function the hardware starts the process of clearing the
* counter. This process takes some time before it completes depending on the
* selected oscillator (up to 4 peripheral clock cycles). When using the
* ULFRCO for instance as the oscillator the watchdog runs on a 1 kHz clock
* and a watchdog clear operation might take up to 4 ms.
*
* If the device enters EM2 or EM3 while a command is in progress then that
* command will be aborted. An application can use @ref WDOGn_SyncWait()
* to wait for a command to complete.
*
* @param[in] wdog
* A pointer to the WDOG peripheral register block.
******************************************************************************/
void WDOGn_Feed(WDOG_TypeDef *wdog)
{
#if (_SILICON_LABS_32B_SERIES < 2)
// WDOG should not be fed while it is disabled.
if (!(wdog->CTRL & WDOG_CTRL_EN)) {
return;
}
// If a previous clearing is synchronized to the LF domain, there
// is no point in waiting for it to complete before clearing over again.
// This avoids stalling the core in the typical use case where some idle loop
// keeps clearing WDOG.
if (wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) {
return;
}
// Before writing to the WDOG_CMD register, make sure that
// any previous write to the WDOG_CTRL is complete.
while ( (wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U ) {
}
wdog->CMD = WDOG_CMD_CLEAR;
#else // Series 2 devices
CORE_DECLARE_IRQ_STATE;
// WDOG should not be fed while it is disabled.
if ((wdog->EN & WDOG_EN_EN) == 0U) {
return;
}
// We need an atomic section around the check for sync and the clear command
// because sending a clear command while a previous command is being synchronized
// will cause a BusFault.
CORE_ENTER_ATOMIC();
if ((wdog->SYNCBUSY & WDOG_SYNCBUSY_CMD) == 0U) {
wdog->CMD = WDOG_CMD_CLEAR;
}
CORE_EXIT_ATOMIC();
#endif
}
/***************************************************************************//**
* @brief
* Initialize WDOG (assuming the WDOG configuration has not been
* locked).
*
* @note
* This function modifies the WDOG CTRL register which requires
* synchronization into the low-frequency domain. If this register is modified
* before a previous update to the same register has completed, this function
* will stall until the previous synchronization has completed.
*
* @param[in] wdog
* Pointer to the WDOG peripheral register block.
*
* @param[in] init
* The structure holding the WDOG configuration. A default setting
* #WDOG_INIT_DEFAULT is available for initialization.
******************************************************************************/
void WDOGn_Init(WDOG_TypeDef *wdog, const WDOG_Init_TypeDef *init)
{
#if defined(_WDOG_CFG_MASK)
// Handle series-2 devices
if (wdog->EN != 0U) {
while (wdog->SYNCBUSY != 0U) {
// Wait for any potential synchronization to finish
}
wdog->EN_CLR = WDOG_EN_EN;
#if defined(_WDOG_EN_DISABLING_MASK)
while (wdog->EN & _WDOG_EN_DISABLING_MASK) {
/* Wait for disabling to finish */
}
#endif
}
wdog->CFG = (init->debugRun ? WDOG_CFG_DEBUGRUN : 0U)
| (init->clrSrc ? WDOG_CFG_CLRSRC : 0U)
#if defined(_WDOG_CFG_EM1RUN_MASK)
| (init->em1Run ? WDOG_CFG_EM1RUN : 0U)
#endif
| (init->em2Run ? WDOG_CFG_EM2RUN : 0U)
| (init->em3Run ? WDOG_CFG_EM3RUN : 0U)
| (init->em4Block ? WDOG_CFG_EM4BLOCK : 0U)
| (init->prs0MissRstEn ? WDOG_CFG_PRS0MISSRSTEN : 0U)
| (init->prs1MissRstEn ? WDOG_CFG_PRS1MISSRSTEN : 0U)
| (init->resetDisable ? WDOG_CFG_WDOGRSTDIS : 0U)
| ((uint32_t)(init->warnSel) << _WDOG_CFG_WARNSEL_SHIFT)
| ((uint32_t)(init->winSel) << _WDOG_CFG_WINSEL_SHIFT)
| ((uint32_t)(init->perSel) << _WDOG_CFG_PERSEL_SHIFT);
WDOGn_Enable(wdog, init->enable);
if (init->lock) {
WDOGn_Lock(wdog);
}
#else
// Handle series-0 and series-1 devices
uint32_t setting;
setting = (init->enable ? WDOG_CTRL_EN : 0U)
| (init->debugRun ? WDOG_CTRL_DEBUGRUN : 0U)
#if defined(_WDOG_CTRL_CLRSRC_MASK)
| (init->clrSrc ? WDOG_CTRL_CLRSRC : 0U)
#endif
| (init->em2Run ? WDOG_CTRL_EM2RUN : 0U)
| (init->em3Run ? WDOG_CTRL_EM3RUN : 0U)
| (init->em4Block ? WDOG_CTRL_EM4BLOCK : 0U)
| (init->swoscBlock ? WDOG_CTRL_SWOSCBLOCK : 0U)
| (init->lock ? WDOG_CTRL_LOCK : 0U)
| ((uint32_t)(init->clkSel) << _WDOG_CTRL_CLKSEL_SHIFT)
| ((uint32_t)(init->perSel) << _WDOG_CTRL_PERSEL_SHIFT);
#if defined(_WDOG_CTRL_WDOGRSTDIS_MASK)
setting |= (init->resetDisable ? WDOG_CTRL_WDOGRSTDIS : 0U);
#endif
#if defined(_WDOG_CTRL_WARNSEL_MASK)
setting |= ((uint32_t)(init->warnSel) << _WDOG_CTRL_WARNSEL_SHIFT);
#endif
#if defined(_WDOG_CTRL_WINSEL_MASK)
setting |= ((uint32_t)(init->winSel) << _WDOG_CTRL_WINSEL_SHIFT);
#endif
// Wait for previous operations/modifications to complete
int i = 0;
while (((wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U)
&& (i < WDOG_SYNC_TIMEOUT)) {
i++;
}
wdog->CTRL = setting;
#endif
}
/***************************************************************************//**
* @brief
* Lock the WDOG configuration.
*
* @details
* This prevents errors from overwriting the WDOG configuration, possibly
* disabling it. Only a reset can unlock the WDOG configuration once locked.
*
* If the LFRCO or LFXO clocks are used to clock WDOG,
* consider using the option of inhibiting those clocks to be disabled.
* See the WDOG_Enable() initialization structure.
*
* @note
* This function modifies the WDOG CTRL register which requires
* synchronization into the low-frequency domain. If this register is modified
* before a previous update to the same register has completed, this function
* will stall until the previous synchronization has completed.
*
* @param[in] wdog
* A pointer to WDOG peripheral register block.
******************************************************************************/
void WDOGn_Lock(WDOG_TypeDef *wdog)
{
#if defined(_WDOG_LOCK_MASK)
wdog->LOCK = _WDOG_LOCK_LOCKKEY_LOCK;
#else
// Wait for any pending previous write operation to have been completed in
// the low-frequency domain.
while ( (wdog->SYNCBUSY & WDOG_SYNCBUSY_CTRL) != 0U ) {
}
// Disable writing to the control register.
BUS_RegBitWrite(&wdog->CTRL, _WDOG_CTRL_LOCK_SHIFT, 1);
#endif
}
/***************************************************************************//**
* @brief
* Wait for the WDOG to complete all synchronization of register changes
* and commands.
*
* @param[in] wdog
* A pointer to WDOG peripheral register block.
******************************************************************************/
void WDOGn_SyncWait(WDOG_TypeDef *wdog)
{
#if defined(_SILICON_LABS_32B_SERIES_2)
while ((wdog->EN != 0U) && (wdog->SYNCBUSY != 0U)) {
// Wait for synchronization to finish
}
#else
while (wdog->SYNCBUSY != 0U) {
// Wait for synchronization to finish
}
#endif
}
/***************************************************************************//**
* @brief
* Unlock the WDOG configuration.
*
* @details
* Note that this function will have no effect on devices where a reset is
* the only way to unlock the watchdog.
*
* @param[in] wdog
* A pointer to WDOG peripheral register block.
******************************************************************************/
void WDOGn_Unlock(WDOG_TypeDef *wdog)
{
#if defined(_WDOG_LOCK_MASK)
wdog->LOCK = _WDOG_LOCK_LOCKKEY_UNLOCK;
#else
(void) wdog;
#endif
}
/** @} (end addtogroup wdog) */
#endif /* defined(WDOG_COUNT) && (WDOG_COUNT > 0) */