/***************************************************************************//**
 * @file
 * @brief Low Energy Sensor (LESENSE) 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_lesense.h"

#if defined(LESENSE_COUNT) && (LESENSE_COUNT > 0)
#include "em_assert.h"
#include "em_bus.h"
#include "em_cmu.h"

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
#if !defined(UINT32_MAX)
#define UINT32_MAX ((uint32_t)(0xFFFFFFFF))
#endif
/** @endcond */

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

/***************************************************************************//**
 * @addtogroup LESENSE
 * @brief Low Energy Sensor (LESENSE) Peripheral API
 * @details
 *  This module contains functions to control the LESENSE peripheral of Silicon
 *  Labs 32-bit MCUs and SoCs. LESENSE is a low-energy sensor interface capable
 *  of autonomously collecting and processing data from multiple sensors even
 *  when in EM2.
 * @{
 ******************************************************************************/

/** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
#if defined(_LESENSE_ROUTE_MASK)
#define GENERIC_LESENSE_ROUTE    LESENSE->ROUTE
#else
#define GENERIC_LESENSE_ROUTE    LESENSE->ROUTEPEN
#endif

#if defined(_SILICON_LABS_32B_SERIES_0)
/* DACOUT mode only available on channel 0, 1, 2, 3, 12, 13, 14, 15 */
#define DACOUT_SUPPORT  0xF00F
#else
/* DACOUT mode only available on channel 4, 5, 7, 10, 12, 13 */
#define DACOUT_SUPPORT  0x34B0
#endif
/** @endcond */

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

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

/***************************************************************************//**
 * @brief
 *   Initialize the LESENSE module.
 *
 * @details
 *   This function configures the main parameters of the LESENSE interface.
 *   See the initialization parameter type definition
 *   (@ref LESENSE_Init_TypeDef) for more details.
 *
 * @note
 *   @ref LESENSE_Init() is designed to initialize LESENSE once in an
 *   operation cycle. Be aware of the effects of reconfiguration if using this
 *   function from multiple sources in your code. This function has not been
 *   designed to be re-entrant.
 *   Requesting reset by setting @p reqReset to true is required in each reset
 *   or power-on cycle to configure the default values of the RAM
 *   mapped LESENSE registers.
 *   Notice that GPIO pins used by the LESENSE module must be properly
 *   configured by the user explicitly for the LESENSE to work as
 *   intended.
 *   (When configuring pins, one should remember to consider the sequence of
 *   configuration to avoid unintended pulses/glitches on output
 *   pins.)
 *
 * @param[in] init
 *   The LESENSE initialization structure.
 *
 * @param[in] reqReset
 *   Request to call @ref LESENSE_Reset() first to initialize all
 *   LESENSE registers with default values.
 ******************************************************************************/
void LESENSE_Init(const LESENSE_Init_TypeDef * init, bool reqReset)
{
  /* Sanity check of initialization values. */
  EFM_ASSERT((uint32_t)init->timeCtrl.startDelay < 4U);
#if defined(_LESENSE_PERCTRL_DACPRESC_MASK)
  EFM_ASSERT((uint32_t)init->perCtrl.dacPresc < 32U);
#endif

  /* Reset LESENSE registers if requested. */
  if (reqReset) {
    LESENSE_Reset();
  }

  /* Set sensor start delay for each channel. */
  LESENSE_StartDelaySet((uint32_t)init->timeCtrl.startDelay);
#if defined(_LESENSE_TIMCTRL_AUXSTARTUP_MASK)
  /* Configure the AUXHRFCO startup delay. */
  LESENSE->TIMCTRL = (LESENSE->TIMCTRL & (~_LESENSE_TIMCTRL_AUXSTARTUP_MASK))
                     | (init->timeCtrl.delayAuxStartup << _LESENSE_TIMCTRL_AUXSTARTUP_SHIFT);
#endif

  /* LESENSE core control configuration.
   * Set the PRS source, SCANCONF register usage strategy, interrupt and
   * DMA trigger level condition, DMA wakeup condition, bias mode,
   * enable/disable to sample both ACMPs simultaneously, enable/disable to store
   * SCANRES in CNT_RES after each scan, enable/disable to always write to the
   * result buffer, even if it is full, and enable/disable LESENSE running in debug
   * mode. */
  LESENSE->CTRL =
    ((uint32_t)init->coreCtrl.prsSel         << _LESENSE_CTRL_PRSSEL_SHIFT)
    | (uint32_t)init->coreCtrl.scanConfSel
    | (uint32_t)init->coreCtrl.bufTrigLevel
    | (uint32_t)init->coreCtrl.wakeupOnDMA
#if defined(_LESENSE_CTRL_ACMP0INV_MASK)
    | ((uint32_t)init->coreCtrl.invACMP0     << _LESENSE_CTRL_ACMP0INV_SHIFT)
    | ((uint32_t)init->coreCtrl.invACMP1     << _LESENSE_CTRL_ACMP1INV_SHIFT)
#endif
    | ((uint32_t)init->coreCtrl.dualSample   << _LESENSE_CTRL_DUALSAMPLE_SHIFT)
    | ((uint32_t)init->coreCtrl.storeScanRes << _LESENSE_CTRL_STRSCANRES_SHIFT)
    | ((uint32_t)init->coreCtrl.bufOverWr    << _LESENSE_CTRL_BUFOW_SHIFT)
    | ((uint32_t)init->coreCtrl.debugRun     << _LESENSE_CTRL_DEBUGRUN_SHIFT);

  /* Set scan mode in the CTRL register using the provided function. Don't
   * start scanning immediately. */
  LESENSE_ScanModeSet((LESENSE_ScanMode_TypeDef)init->coreCtrl.scanStart, false);

  /* The LESENSE peripheral control configuration.
   * Set DAC0 and DAC1 data source, conversion mode, and output mode. Set the DAC
   * prescaler and reference. Set ACMP0 and ACMP1 control mode. Set the ACMP and DAC
   * duty cycle (warm up) mode. */
  LESENSE->PERCTRL = 0
#if defined(_LESENSE_PERCTRL_DACCH0EN_MASK)
                     | ((uint32_t)init->perCtrl.dacCh0En       << _LESENSE_PERCTRL_DACCH0EN_SHIFT)
                     | ((uint32_t)init->perCtrl.dacCh1En       << _LESENSE_PERCTRL_DACCH1EN_SHIFT)
#endif
                     | ((uint32_t)init->perCtrl.dacCh0Data     << _LESENSE_PERCTRL_DACCH0DATA_SHIFT)
                     | ((uint32_t)init->perCtrl.dacCh1Data     << _LESENSE_PERCTRL_DACCH1DATA_SHIFT)
#if defined(_LESENSE_PERCTRL_DACCH0CONV_MASK)
                     | ((uint32_t)init->perCtrl.dacCh0ConvMode << _LESENSE_PERCTRL_DACCH0CONV_SHIFT)
                     | ((uint32_t)init->perCtrl.dacCh0OutMode  << _LESENSE_PERCTRL_DACCH0OUT_SHIFT)
                     | ((uint32_t)init->perCtrl.dacCh1ConvMode << _LESENSE_PERCTRL_DACCH1CONV_SHIFT)
                     | ((uint32_t)init->perCtrl.dacCh1OutMode  << _LESENSE_PERCTRL_DACCH1OUT_SHIFT)
                     | ((uint32_t)init->perCtrl.dacPresc       << _LESENSE_PERCTRL_DACPRESC_SHIFT)
                     | (uint32_t)init->perCtrl.dacRef
#endif
#if defined(_LESENSE_PERCTRL_DACCONVTRIG_MASK)
                     | ((uint32_t)init->perCtrl.dacStartupHalf << _LESENSE_PERCTRL_DACSTARTUP_SHIFT)
                     | ((uint32_t)init->perCtrl.dacScan        << _LESENSE_PERCTRL_DACCONVTRIG_SHIFT)
#endif
                     | ((uint32_t)init->perCtrl.acmp0Mode      << _LESENSE_PERCTRL_ACMP0MODE_SHIFT)
                     | ((uint32_t)init->perCtrl.acmp1Mode      << _LESENSE_PERCTRL_ACMP1MODE_SHIFT)
#if defined(_LESENSE_PERCTRL_ACMP0INV_MASK)
                     | ((uint32_t)init->coreCtrl.invACMP0      << _LESENSE_PERCTRL_ACMP0INV_SHIFT)
                     | ((uint32_t)init->coreCtrl.invACMP1      << _LESENSE_PERCTRL_ACMP1INV_SHIFT)
#endif
                     | (uint32_t)init->perCtrl.warmupMode;

  /* The LESENSE decoder general control configuration.
   * Set the decoder input source and select PRS input for decoder bits.
   * Enable/disable the decoder to check the present state.
   * Enable/disable decoder to channel interrupt mapping.
   * Enable/disable decoder hysteresis on the PRS output.
   * Enable/disable decoder hysteresis on count events.
   * Enable/disable decoder hysteresis on interrupt requests.
   * Enable/disable count mode on LESPRS0 and LESPRS1. */
  LESENSE->DECCTRL =
    (uint32_t)init->decCtrl.decInput
    | ((uint32_t)init->decCtrl.prsChSel0 << _LESENSE_DECCTRL_PRSSEL0_SHIFT)
    | ((uint32_t)init->decCtrl.prsChSel1 << _LESENSE_DECCTRL_PRSSEL1_SHIFT)
    | ((uint32_t)init->decCtrl.prsChSel2 << _LESENSE_DECCTRL_PRSSEL2_SHIFT)
    | ((uint32_t)init->decCtrl.prsChSel3 << _LESENSE_DECCTRL_PRSSEL3_SHIFT)
    | ((uint32_t)init->decCtrl.chkState  << _LESENSE_DECCTRL_ERRCHK_SHIFT)
    | ((uint32_t)init->decCtrl.intMap    << _LESENSE_DECCTRL_INTMAP_SHIFT)
    | ((uint32_t)init->decCtrl.hystPRS0  << _LESENSE_DECCTRL_HYSTPRS0_SHIFT)
    | ((uint32_t)init->decCtrl.hystPRS1  << _LESENSE_DECCTRL_HYSTPRS1_SHIFT)
    | ((uint32_t)init->decCtrl.hystPRS2  << _LESENSE_DECCTRL_HYSTPRS2_SHIFT)
    | ((uint32_t)init->decCtrl.hystIRQ   << _LESENSE_DECCTRL_HYSTIRQ_SHIFT)
    | ((uint32_t)init->decCtrl.prsCount  << _LESENSE_DECCTRL_PRSCNT_SHIFT);

  /* Set the initial LESENSE decoder state. */
  LESENSE_DecoderStateSet((uint32_t)init->decCtrl.initState);

  /* The LESENSE bias control configuration. */
  LESENSE->BIASCTRL = (uint32_t)init->coreCtrl.biasMode;
}

/***************************************************************************//**
 * @brief
 *   Set the scan frequency for periodic scanning.
 *
 * @details
 *   This function only applies to LESENSE if a period counter is used as
 *   a trigger for scan start.
 *   The calculation is based on the following formula:
 *   Fscan = LFACLKles / ((1+PCTOP)*2^PCPRESC)
 *
 * @note
 *   Note that the calculation does not necessarily result in the requested
 *   scan frequency due to integer division. Check the return value for the
 *   resulted scan frequency.
 *
 * @param[in] refFreq
 *   Select reference LFACLK clock frequency in Hz. If set to 0, the current
 *   clock frequency is being used as a reference.
 *
 * @param[in] scanFreq
 *   Set the desired scan frequency in Hz.
 *
 * @return
 *   Frequency in Hz calculated and set by this function. Users can use this to
 *   compare the requested and set values.
 ******************************************************************************/
uint32_t LESENSE_ScanFreqSet(uint32_t refFreq, uint32_t scanFreq)
{
  uint32_t tmp;
  uint32_t pcPresc = 0UL;  /* Period counter prescaler. */
  uint32_t clkDiv  = 1UL;  /* Clock divisor value (2^pcPresc). */
  uint32_t pcTop   = 63UL; /* Period counter top value (max. 63). */
  uint32_t calcScanFreq;   /* Variable for testing the calculation algorithm. */

  /* If refFreq is set to 0, the currently-configured reference clock is
   * assumed. */
  if (!refFreq) {
    refFreq = CMU_ClockFreqGet(cmuClock_LESENSE);
  }

  /* The maximum value of pcPresc is 128. AS a result, using the reference frequency less than
   * 33554431 Hz (33.554431 MHz), the frequency calculation in the while loop
   * below will not overflow. */
  EFM_ASSERT(refFreq < ((uint32_t)UINT32_MAX / 128UL));

  /* A sanity check of scan frequency value. */
  EFM_ASSERT((scanFreq > 0U) && (scanFreq <= refFreq));

  /* Calculate the minimum necessary prescaler value to provide the
   * biggest possible resolution for setting the scan frequency.
   * The maximum number of calculation cycles is 7 (value of lesenseClkDiv_128). */
  while ((refFreq / ((uint32_t)scanFreq * clkDiv) > (pcTop + 1UL))
         && (pcPresc < lesenseClkDiv_128)) {
    ++pcPresc;
    clkDiv = (uint32_t)1UL << pcPresc;
  }

  /* Calculate the pcTop value. */
  pcTop = ((uint32_t)refFreq / ((uint32_t)scanFreq * clkDiv)) - 1UL;

  /* Clear current PCPRESC and PCTOP settings. Be aware of the effect of
   * non-atomic Read-Modify-Write on LESENSE->TIMCRTL. */
  tmp = LESENSE->TIMCTRL & (~_LESENSE_TIMCTRL_PCPRESC_MASK
                            & ~_LESENSE_TIMCTRL_PCTOP_MASK);

  /* Set new values in tmp while reserving other settings. */
  tmp |= ((uint32_t)pcPresc << _LESENSE_TIMCTRL_PCPRESC_SHIFT)
         | ((uint32_t)pcTop << _LESENSE_TIMCTRL_PCTOP_SHIFT);

  /* Set values in the LESENSE_TIMCTRL register. */
  LESENSE->TIMCTRL = tmp;

  /* For testing the calculation algorithm. */
  calcScanFreq = ((uint32_t)refFreq / ((uint32_t)(1UL + pcTop) * clkDiv));

  return calcScanFreq;
}

/***************************************************************************//**
 * @brief
 *   Set scan mode of the LESENSE channels.
 *
 * @details
 *   This function configures how the scan start is triggered. It can be
 *   used for re-configuring the scan mode while running the application but it
 *   is also used by LESENSE_Init() for initialization.
 *
 * @note
 *   Users can configure the scan mode by LESENSE_Init() function, but only with
 *   a significant overhead. This simple function serves the purpose of
 *   controlling this parameter after the channel has been configured.
 *   Be aware of the effects of the non-atomic Read-Modify-Write cycle.
 *
 * @param[in] scanMode
 *   Select the location to map LESENSE alternate excitation channels.
 *   @li lesenseScanStartPeriodic - A new scan is started each time the period
 *                                  counter overflows.
 *   @li lesenseScanStartOneShot - A single scan is performed when
 *                                 LESENSE_ScanStart() is called.
 *   @li lesenseScanStartPRS - A new scan is triggered by pulse on the PRS channel.
 *
 * @param[in] start
 *   If true, LESENSE_ScanStart() is immediately issued after configuration.
 ******************************************************************************/
void LESENSE_ScanModeSet(LESENSE_ScanMode_TypeDef scanMode,
                         bool start)
{
  uint32_t tmp; /* temporary storage of the CTRL register value */

  /* Save the CTRL register value to tmp.
   * Be aware of the effects of the non-atomic Read-Modify-Write cycle. */
  tmp = LESENSE->CTRL & ~(_LESENSE_CTRL_SCANMODE_MASK);
  /* Setting the requested scanMode to the CTRL register. Casting signed int
   * (enumeration) to unsigned long (uint32_t). */
  tmp |= (uint32_t)scanMode;

  /* Write the new value to the CTRL register. */
  LESENSE->CTRL = tmp;

  /* Start the sensor scanning if requested. */
  if (start) {
    LESENSE_ScanStart();
  }
}

/***************************************************************************//**
 * @brief
 *   Set the start delay of the sensor interaction on each channel.
 *
 * @details
 *   This function sets the start delay of the sensor interaction on each channel.
 *   It can be used for adjusting the start delay while running the application
 *   but it is also used by LESENSE_Init() for initialization.
 *
 * @note
 *   Users can configure the start delay by LESENSE_Init() function, but only
 *   with a significant overhead. This simple function serves the purpose of
 *   controlling this parameter after the channel has been configured.
 *   Be aware of the effects of the non-atomic Read-Modify-Write cycle.
 *
 * @param[in] startDelay
 *   A number of LFACLK cycles to delay. A valid range: 0-3 (2 bit).
 ******************************************************************************/
void LESENSE_StartDelaySet(uint8_t startDelay)
{
  uint32_t tmp; /* Temporary storage of the TIMCTRL register value */

  /* Sanity check of the startDelay. */
  EFM_ASSERT(startDelay < 4U);

  /* Save the TIMCTRL register value to tmp.
   * Be aware of the effects of the non-atomic Read-Modify-Write cycle. */
  tmp = LESENSE->TIMCTRL & ~(_LESENSE_TIMCTRL_STARTDLY_MASK);
  /* Setting the requested startDelay to the TIMCTRL register. */
  tmp |= (uint32_t)startDelay << _LESENSE_TIMCTRL_STARTDLY_SHIFT;

  /* Write the new value to the TIMCTRL register. */
  LESENSE->TIMCTRL = tmp;
}

/***************************************************************************//**
 * @brief
 *   Set the clock division for LESENSE timers.
 *
 * @details
 *   Use this function to configure the clock division for the LESENSE timers
 *   used for excitation timing.
 *   The division setting is global but the clock source can be selected for
 *   each channel using LESENSE_ChannelConfig() function. See
 *   documentation for more details.
 *
 * @note
 *   If AUXHFRCO is used for excitation timing, LFACLK can't exceed 500 kHz.
 *   LFACLK can't exceed 50 kHz if the ACMP threshold level (ACMPTHRES) is not
 *   equal for all channels.
 *
 * @param[in] clk
 *   Select the clock to prescale.
 *    @li lesenseClkHF - set AUXHFRCO clock divisor for HF timer.
 *    @li lesenseClkLF - set LFACLKles clock divisor for LF timer.
 *
 * @param[in] clkDiv
 *   The clock divisor value. A valid range depends on the @p clk value.
 ******************************************************************************/
void LESENSE_ClkDivSet(LESENSE_ChClk_TypeDef clk,
                       LESENSE_ClkPresc_TypeDef clkDiv)
{
  uint32_t tmp;

  /* Select the clock to prescale. */
  switch (clk) {
    case lesenseClkHF:
      /* A sanity check of the clock divisor for the HF clock. */
      EFM_ASSERT((uint32_t)clkDiv <= lesenseClkDiv_8);

      /* Clear the current AUXPRESC settings. */
      tmp = LESENSE->TIMCTRL & ~(_LESENSE_TIMCTRL_AUXPRESC_MASK);

      /* Set the new values in tmp while reserving other settings. */
      tmp |= ((uint32_t)clkDiv << _LESENSE_TIMCTRL_AUXPRESC_SHIFT);

      /* Set values in LESENSE_TIMCTRL register. */
      LESENSE->TIMCTRL = tmp;
      break;

    case lesenseClkLF:
      /* Clear current LFPRESC settings. */
      tmp = LESENSE->TIMCTRL & ~(_LESENSE_TIMCTRL_LFPRESC_MASK);

      /* Set new values in tmp while reserving other settings. */
      tmp |= ((uint32_t)clkDiv << _LESENSE_TIMCTRL_LFPRESC_SHIFT);

      /* Set values in the LESENSE_TIMCTRL register. */
      LESENSE->TIMCTRL = tmp;
      break;

    default:
      EFM_ASSERT(0);
      break;
  }
}

/***************************************************************************//**
 * @brief
 *   Configure all (16) LESENSE sensor channels.
 *
 * @details
 *   This function configures all sensor channels of the LESENSE interface.
 *   See the configuration parameter type definition
 *   (LESENSE_ChAll_TypeDef) for more details.
 *
 * @note
 *   Channels can be configured individually using LESENSE_ChannelConfig()
 *   function.
 *   Notice that pins used by the LESENSE module must be properly configured
 *   by the user explicitly for LESENSE to work as intended.
 *   (When configuring pins, consider the sequence of the
 *   configuration to avoid unintended pulses/glitches on output
 *   pins.)
 *
 * @param[in] confChAll
 *   A configuration structure for all (16) LESENSE sensor channels.
 ******************************************************************************/
void LESENSE_ChannelAllConfig(const LESENSE_ChAll_TypeDef * confChAll)
{
  uint32_t i;

  /* Iterate through all 16 channels. */
  for (i = 0U; i < LESENSE_NUM_CHANNELS; ++i) {
    /* Configure scan channels. */
    LESENSE_ChannelConfig(&confChAll->Ch[i], i);
  }
}

/***************************************************************************//**
 * @brief
 *   Configure a single LESENSE sensor channel.
 *
 * @details
 *   This function configures a single sensor channel of the LESENSE interface.
 *   See the configuration parameter type definition
 *   (LESENSE_ChDesc_TypeDef) for more details.
 *
 * @note
 *   This function has been designed to minimize the effects of sensor channel
 *   reconfiguration while LESENSE is in operation. However, be aware
 *   of these effects and the right timing to call this function.
 *   Parameter @p useAltEx must be true in the channel configuration to
 *   use alternate excitation pins.
 *
 * @param[in] confCh
 *   A configuration structure for a single LESENSE sensor channel.
 *
 * @param[in] chIdx
 *   A channel index to configure (0-15).
 ******************************************************************************/
void LESENSE_ChannelConfig(const LESENSE_ChDesc_TypeDef * confCh,
                           uint32_t chIdx)
{
  uint32_t tmp; /* A service variable. */

  /* A sanity check of configuration parameters. */
  EFM_ASSERT(chIdx < LESENSE_NUM_CHANNELS);
  EFM_ASSERT(confCh->exTime      <= (_LESENSE_CH_TIMING_EXTIME_MASK >> _LESENSE_CH_TIMING_EXTIME_SHIFT));
  EFM_ASSERT(confCh->measDelay   <= (_LESENSE_CH_TIMING_MEASUREDLY_MASK >> _LESENSE_CH_TIMING_MEASUREDLY_SHIFT));
#if defined(_SILICON_LABS_32B_SERIES_0)
  // Sample delay on other devices are 8 bits which fits perfectly in uint8_t.
  EFM_ASSERT(confCh->sampleDelay <= (_LESENSE_CH_TIMING_SAMPLEDLY_MASK >> _LESENSE_CH_TIMING_SAMPLEDLY_SHIFT));
#endif

  /* Not a complete assert, as the maximum value of acmpThres depends on other
   * configuration parameters. Check the parameter description of acmpThres
   * for more details. */
  EFM_ASSERT(confCh->acmpThres < 4096U);
  if (confCh->chPinExMode == lesenseChPinExDACOut) {
    EFM_ASSERT((0x1 << chIdx) & DACOUT_SUPPORT);
  }

#if defined(_LESENSE_IDLECONF_CH0_DACCH0)
  EFM_ASSERT(!(confCh->chPinIdleMode == lesenseChPinIdleDACCh1
               && ((chIdx != 12U)
                   && (chIdx != 13U)
                   && (chIdx != 14U)
                   && (chIdx != 15U))));
  EFM_ASSERT(!(confCh->chPinIdleMode == lesenseChPinIdleDACCh0
               && ((chIdx != 0U)
                   && (chIdx != 1U)
                   && (chIdx != 2U)
                   && (chIdx != 3U))));
#endif

  /* Configure the chIdx setup in LESENSE idle phase.
   * Read-modify-write to support reconfiguration during the LESENSE
   * operation. */
  tmp               = (LESENSE->IDLECONF & ~((uint32_t)0x3UL << (chIdx * 2UL)));
  tmp              |= ((uint32_t)confCh->chPinIdleMode << (chIdx * 2UL));
  LESENSE->IDLECONF = tmp;

  /* A channel-specific timing configuration on scan channel chIdx.
   * Set excitation time, sampling delay, and measurement delay. */
  LESENSE_ChannelTimingSet(chIdx,
                           confCh->exTime,
                           confCh->sampleDelay,
                           confCh->measDelay);

  /* A channel-specific configuration of clocks, sample mode, excitation pin mode
   * alternate excitation usage, and interrupt mode on scan channel chIdx in
   * LESENSE_CHchIdx_INTERACT. */
  LESENSE->CH[chIdx].INTERACT =
    ((uint32_t)confCh->exClk       << _LESENSE_CH_INTERACT_EXCLK_SHIFT)
    | ((uint32_t)confCh->sampleClk << _LESENSE_CH_INTERACT_SAMPLECLK_SHIFT)
    | (uint32_t)confCh->sampleMode
    | (uint32_t)confCh->intMode
    | (uint32_t)confCh->chPinExMode
    | ((uint32_t)confCh->useAltEx  << _LESENSE_CH_INTERACT_ALTEX_SHIFT);

  /* Configure the channel-specific counter comparison mode, optional result
   * forwarding to decoder, optional counter value storing, and optional result
   * inverting on scan channel chIdx in LESENSE_CHchIdx_EVAL. */
  LESENSE->CH[chIdx].EVAL =
    (uint32_t)confCh->compMode
    | ((uint32_t)confCh->shiftRes    << _LESENSE_CH_EVAL_DECODE_SHIFT)
    | ((uint32_t)confCh->storeCntRes << _LESENSE_CH_EVAL_STRSAMPLE_SHIFT)
    | ((uint32_t)confCh->invRes      << _LESENSE_CH_EVAL_SCANRESINV_SHIFT)
#if defined(_LESENSE_CH_EVAL_MODE_MASK)
    | ((uint32_t)confCh->evalMode    << _LESENSE_CH_EVAL_MODE_SHIFT)
#endif
  ;

  /* Configure the analog comparator (ACMP) threshold and decision threshold for
   * the counter separately with the function provided for that. */
  LESENSE_ChannelThresSet(chIdx,
                          confCh->acmpThres,
                          confCh->cntThres);

  /* Enable/disable interrupts on channel */
  BUS_RegBitWrite(&LESENSE->IEN, chIdx, confCh->enaInt);

  /* Enable/disable CHchIdx pin. */
  BUS_RegBitWrite(&GENERIC_LESENSE_ROUTE, chIdx, confCh->enaPin);

  /* Enable/disable scan channel chIdx. */
  BUS_RegBitWrite(&LESENSE->CHEN, chIdx, confCh->enaScanCh);
}

/***************************************************************************//**
 * @brief
 *   Configure the LESENSE alternate excitation modes.
 *
 * @details
 *   This function configures the alternate excitation channels of the LESENSE
 *   interface. See the configuration parameter type definition
 *   (LESENSE_ConfAltEx_TypeDef) for more details.
 *
 * @note
 *   The @p useAltEx parameter must be true in the channel configuration structure
 *   (LESENSE_ChDesc_TypeDef) to use alternate excitation pins on the
 *   channel.
 *
 * @param[in] confAltEx
 *   A configuration structure for LESENSE alternate excitation pins.
 ******************************************************************************/
void LESENSE_AltExConfig(const LESENSE_ConfAltEx_TypeDef * confAltEx)
{
  uint32_t i;
  uint32_t tmp;

  /* Configure the alternate excitation mapping.
   * Atomic read-modify-write using BUS_RegBitWrite function to
   * support reconfiguration during the LESENSE operation. */
  BUS_RegBitWrite(&LESENSE->CTRL,
                  _LESENSE_CTRL_ALTEXMAP_SHIFT,
                  confAltEx->altExMap);

  switch (confAltEx->altExMap) {
    case lesenseAltExMapALTEX:
      /* Iterate through the 8 possible alternate excitation pin descriptors. */
      for (i = 0U; i < 8U; ++i) {
        /* Enable/disable the alternate excitation pin i.
         * Atomic read-modify-write using BUS_RegBitWrite function to
         * support reconfiguration during the LESENSE operation. */
        BUS_RegBitWrite(&GENERIC_LESENSE_ROUTE,
                        (16UL + i),
                        confAltEx->AltEx[i].enablePin);

        /* Set up the idle phase state of the alternate excitation pin i.
         * Read-modify-write to support reconfiguration during the LESENSE
         * operation. */
        tmp                = (LESENSE->ALTEXCONF & ~((uint32_t)0x3UL << (i * 2UL)));
        tmp               |= ((uint32_t)confAltEx->AltEx[i].idleConf << (i * 2UL));
        LESENSE->ALTEXCONF = tmp;

        /* Enable/disable always excite on channel i. */
        BUS_RegBitWrite(&LESENSE->ALTEXCONF,
                        (16UL + i),
                        confAltEx->AltEx[i].alwaysEx);
      }
      break;

#if defined(_LESENSE_CTRL_ALTEXMAP_ACMP)
    case lesenseAltExMapACMP:
#else
    case lesenseAltExMapCH:
#endif
      /* Iterate through all 16 alternate excitation channels. */
      for (i = 0U; i < 16U; ++i) {
        /* Enable/disable the alternate ACMP excitation channel pin i. */
        /* An atomic read-modify-write using BUS_RegBitWrite function to
         * support reconfiguration during the LESENSE operation. */
        BUS_RegBitWrite(&GENERIC_LESENSE_ROUTE,
                        i,
                        confAltEx->AltEx[i].enablePin);
      }
      break;
    default:
      /* An illegal value. */
      EFM_ASSERT(0);
      break;
  }
}

/***************************************************************************//**
 * @brief
 *   Enable/disable LESENSE scan channel and the pin assigned to it.
 *
 * @details
 *   Use this function to enable/disable a selected LESENSE scan channel and the
 *   pin assigned to it.
 *
 * @note
 *   Users can enable/disable scan channels and the channel pin with
 *   the LESENSE_ChannelConfig() function, but only with a significant overhead.
 *   This simple function controls these parameters
 *   after the channel has been configured.
 *
 * @param[in] chIdx
 *   An identifier of the scan channel. A valid range: 0-15.
 *
 * @param[in] enaScanCh
 *   Enable/disable the selected scan channel by setting this parameter to
 *   true/false respectively.
 *
 * @param[in] enaPin
 *   Enable/disable the pin assigned to the channel selected by @p chIdx.
 ******************************************************************************/
void LESENSE_ChannelEnable(uint8_t chIdx,
                           bool enaScanCh,
                           bool enaPin)
{
  /* Enable/disable the assigned pin of scan channel chIdx.
   * Note: BUS_RegBitWrite() function is used for setting/clearing single
   * bit peripheral register bit fields. Read the function description in
   * em_bus.h for more details. */
  BUS_RegBitWrite(&GENERIC_LESENSE_ROUTE, chIdx, enaPin);

  /* Enable/disable scan channel chIdx. */
  BUS_RegBitWrite(&LESENSE->CHEN, chIdx, enaScanCh);
}

/***************************************************************************//**
 * @brief
 *   Enable/disable LESENSE scan channel and the pin assigned to it.
 *
 * @details
 *   Use this function to enable/disable LESENSE scan channels and the pins
 *   assigned to them using a mask.
 *
 * @note
 *   Users can enable/disable scan channels and channel pins by using the
 *   LESENSE_ChannelAllConfig() function, but only with a significant overhead.
 *   This simple function controls these parameters
 *   after the channel has been configured.
 *
 * @param[in] chMask
 *   Set the corresponding bit to 1 to enable, 0 to disable the selected scan
 *   channel.
 *
 * @param[in] pinMask
 *   Set the corresponding bit to 1 to enable, 0 to disable the pin on selected
 *   channel.
 ******************************************************************************/
void LESENSE_ChannelEnableMask(uint16_t chMask, uint16_t pinMask)
{
  /* Enable/disable all channels at once according to the mask. */
  LESENSE->CHEN = chMask;
  /* Enable/disable all channel pins at once according to the mask. */
  GENERIC_LESENSE_ROUTE = pinMask;
}

/***************************************************************************//**
 * @brief
 *   Set LESENSE channel timing parameters.
 *
 * @details
 *   Use this function to set timing parameters on a selected LESENSE channel.
 *
 * @note
 *   Users can configure the channel timing parameters with the
 *   LESENSE_ChannelConfig() function, but only with a significant overhead.
 *   This simple function controls these parameters
 *   after the channel has been configured.
 *
 * @param[in] chIdx
 *   An identifier of the scan channel. A valid range is 0-15.
 *
 * @param[in] exTime
 *   An excitation time on chIdx. The excitation will last exTime+1 excitation clock
 *   cycles. A valid range is 0-63 (6 bits).
 *
 * @param[in] sampleDelay
 *   Sample delay on chIdx. Sampling will occur after sampleDelay+1 sample clock
 *   cycles. A valid range is 0-127 (7 bits).
 *
 * @param[in] measDelay
 *   A measure delay on chIdx. Sensor measuring is delayed for measDelay+1
 *   excitation clock cycles. A valid range is 0-127 (7 bits).
 ******************************************************************************/
void LESENSE_ChannelTimingSet(uint8_t chIdx,
                              uint8_t exTime,
                              uint8_t sampleDelay,
                              uint16_t measDelay)
{
  /* A sanity check of parameters. */
  EFM_ASSERT(exTime      <= (_LESENSE_CH_TIMING_EXTIME_MASK >> _LESENSE_CH_TIMING_EXTIME_SHIFT));
  EFM_ASSERT(measDelay   <= (_LESENSE_CH_TIMING_MEASUREDLY_MASK >> _LESENSE_CH_TIMING_MEASUREDLY_SHIFT));
#if defined(_SILICON_LABS_32B_SERIES_0)
  // A sample delay on other devices is 8 bits which fits perfectly in uint8_t.
  EFM_ASSERT(sampleDelay <= (_LESENSE_CH_TIMING_SAMPLEDLY_MASK >> _LESENSE_CH_TIMING_SAMPLEDLY_SHIFT));
#endif

  /* A channel-specific timing configuration on the scan channel chIdx.
   * Setting excitation time, sampling delay, and measurement delay. */
  LESENSE->CH[chIdx].TIMING =
    ((uint32_t)exTime        << _LESENSE_CH_TIMING_EXTIME_SHIFT)
    | ((uint32_t)sampleDelay << _LESENSE_CH_TIMING_SAMPLEDLY_SHIFT)
    | ((uint32_t)measDelay   << _LESENSE_CH_TIMING_MEASUREDLY_SHIFT);
}

/***************************************************************************//**
 * @brief
 *   Set LESENSE channel threshold parameters.
 *
 * @details
 *   Use this function to set threshold parameters on a selected LESENSE
 *   channel.
 *
 * @note
 *   Users can configure the channel threshold parameters with the
 *   LESENSE_ChannelConfig() function, but only with a significant overhead.
 *   This simple function serves controls these parameters
 *   after the channel has been configured.
 *
 * @param[in] chIdx
 *   An identifier of the scan channel. A valid range is 0-15.
 *
 * @param[in] acmpThres
 *   ACMP threshold.
 *   @li If perCtrl.dacCh0Data or perCtrl.dacCh1Data is set to
 *   #lesenseDACIfData, acmpThres defines the 12-bit DAC data in the
 *   corresponding data register of the DAC interface (DACn_CH0DATA and
 *   DACn_CH1DATA). In this case, the valid range is 0-4095 (12 bits).
 *
 *   @li If perCtrl.dacCh0Data or perCtrl.dacCh1Data is set to
 *   #lesenseACMPThres, acmpThres defines the 6-bit Vdd scaling factor of ACMP
 *   negative input (VDDLEVEL in ACMP_INPUTSEL register). In this case, the
 *   valid range is 0-63 (6 bits).
 *
 * @param[in] cntThres
 *   A decision threshold for counter comparison.
 *   A valid range is 0-65535 (16 bits).
 ******************************************************************************/
void LESENSE_ChannelThresSet(uint8_t chIdx,
                             uint16_t acmpThres,
                             uint16_t cntThres)
{
  uint32_t tmp; /* A temporary storage */

  /* A sanity check for acmpThres only, cntThres is a 16 bit value. */
  EFM_ASSERT(acmpThres < 4096U);
  /* A sanity check for the LESENSE channel ID. */
  EFM_ASSERT(chIdx < LESENSE_NUM_CHANNELS);

  /* Save the INTERACT register value of channel chIdx to tmp.
   * Be aware of the effects of the non-atomic Read-Modify-Write cycle. */
  tmp = LESENSE->CH[chIdx].INTERACT & ~(0xFFF);
  /* Set the ACMP threshold value to the INTERACT register of the channel chIdx. */
  tmp |= (uint32_t)acmpThres;
  /* Write the new value to the INTERACT register. */
  LESENSE->CH[chIdx].INTERACT = tmp;

  /* Save the EVAL register value of channel chIdx to tmp.
   * Be aware of the effects of the non-atomic Read-Modify-Write cycle. */
  tmp = LESENSE->CH[chIdx].EVAL & ~(_LESENSE_CH_EVAL_COMPTHRES_MASK);
  /* Set the counter threshold value to the INTERACT register of the channel chIdx. */
  tmp |= (uint32_t)cntThres << _LESENSE_CH_EVAL_COMPTHRES_SHIFT;
  /* Write the new value to the EVAL register. */
  LESENSE->CH[chIdx].EVAL = tmp;
}

#if defined(_LESENSE_CH_EVAL_MODE_MASK)
/***************************************************************************//**
 * @brief
 *   Configure a Sliding Window evaluation mode for a specific channel.
 *
 * @details
 *   This function will configure the evaluation mode, the initial
 *   sensor measurement (COMPTHRES), and the window size. For other channel-related
 *   configuration, see the @ref LESENSE_ChannelConfig() function.
 *
 * @warning
 *   Note that the step size and window size configuration are global to all
 *   LESENSE channels and use the same register field in the hardware. This
 *   means that any windowSize configuration passed to this function will
 *   apply for all channels and override all other stepSize/windowSize
 *   configurations.
 *
 * @param[in] chIdx
 *   An identifier of the scan channel. A valid range is 0-15.
 *
 * @param[in] windowSize
 *   A window size to be used on all channels.
 *
 * @param[in] initValue
 *   The initial sensor value for the channel.
 ******************************************************************************/
void LESENSE_ChannelSlidingWindow(uint8_t chIdx,
                                  uint32_t windowSize,
                                  uint32_t initValue)
{
  LESENSE_CH_TypeDef * ch = &LESENSE->CH[chIdx];

  LESENSE_WindowSizeSet(windowSize);
  ch->EVAL = (ch->EVAL & ~(_LESENSE_CH_EVAL_COMPTHRES_MASK | _LESENSE_CH_EVAL_MODE_MASK))
             | (initValue << _LESENSE_CH_EVAL_COMPTHRES_SHIFT)
             | LESENSE_CH_EVAL_MODE_SLIDINGWIN;
}

/***************************************************************************//**
 * @brief
 *   Configure the step detection evaluation mode for a specific channel.
 *
 * @details
 *   This function will configure the evaluation mode, the initial
 *   sensor measurement (COMPTHRES) and the window size. For other channel-related
 *   configuration, see the @ref LESENSE_ChannelConfig() function.
 *
 * @warning
 *   Note that the step size and window size configuration are global to all
 *   LESENSE channels and use the same register field in the hardware. This
 *   means that any stepSize configuration passed to this function will
 *   apply for all channels and override all other stepSize/windowSize
 *   configurations.
 *
 * @param[in] chIdx
 *   An identifier of the scan channel. A valid range is 0-15.
 *
 * @param[in] stepSize
 *   A step size to be used on all channels.
 *
 * @param[in] initValue
 *   The initial sensor value for the channel.
 ******************************************************************************/
void LESENSE_ChannelStepDetection(uint8_t chIdx,
                                  uint32_t stepSize,
                                  uint32_t initValue)
{
  LESENSE_CH_TypeDef * ch = &LESENSE->CH[chIdx];

  LESENSE_StepSizeSet(stepSize);
  ch->EVAL = (ch->EVAL & ~(_LESENSE_CH_EVAL_COMPTHRES_MASK | _LESENSE_CH_EVAL_MODE_MASK))
             | (initValue << _LESENSE_CH_EVAL_COMPTHRES_SHIFT)
             | LESENSE_CH_EVAL_MODE_STEPDET;
}

/***************************************************************************//**
 * @brief
 *   Set the window size for all LESENSE channels.
 *
 * @details
 *   The window size is used by all channels that are configured as
 *   @ref lesenseEvalModeSlidingWindow.
 *
 * @warning
 *   The window size configuration is using the same register field as the
 *   step detection size. As a result, the window size configuration will have an
 *   effect on channels configured with the @ref lesenseEvalModeStepDetection
 *   evaluation mode as well.
 *
 * @param[in] windowSize
 *   The window size to use for all channels.
 ******************************************************************************/
void LESENSE_WindowSizeSet(uint32_t windowSize)
{
  LESENSE->EVALCTRL = (LESENSE->EVALCTRL & ~_LESENSE_EVALCTRL_WINSIZE_MASK)
                      | windowSize;
}

/***************************************************************************//**
 * @brief
 *   Set the step size for all LESENSE channels.
 *
 * @details
 *   The step size is configured using the same register field as used to
 *   configure window size. Therefore, calling this function will overwrite any
 *   previously configured window size as done by the
 *   @ref LESENSE_WindowSizeSet() function.
 *
 * @param[in] stepSize
 *   The step size to use for all channels.
 ******************************************************************************/
void LESENSE_StepSizeSet(uint32_t stepSize)
{
  LESENSE_WindowSizeSet(stepSize);
}
#endif

/***************************************************************************//**
 * @brief
 *   Configure all LESENSE decoder states.
 *
 * @details
 *   This function configures all the decoder states of the LESENSE interface.
 *   See the configuration parameter type definition
 *   (LESENSE_DecStAll_TypeDef) for more details.
 *
 * @note
 *   Decoder states can be configured individually using
 *   LESENSE_DecoderStateConfig() function.
 *
 * @param[in] confDecStAll
 *   A configuration structure for all (16 or 32) LESENSE decoder states.
 ******************************************************************************/
void LESENSE_DecoderStateAllConfig(const LESENSE_DecStAll_TypeDef * confDecStAll)
{
  uint32_t i;

  /* Iterate through all 16 or 32 decoder states. */
  for (i = 0U; i < LESENSE_NUM_DECODER_STATES; ++i) {
    /* A configure decoder state i. */
    LESENSE_DecoderStateConfig(&confDecStAll->St[i], i);
  }
}

/***************************************************************************//**
 * @brief
 *   Configure a single LESENSE decoder state.
 *
 * @details
 *   This function configures a single decoder state of the LESENSE interface.
 *   See the configuration parameter type definition
 *   (LESENSE_DecStDesc_TypeDef) for more details.
 *
 * @param[in] confDecSt
 *   A configuration structure for a single LESENSE decoder state.
 *
 * @param[in] decSt
 *   A decoder state index to configure (0-15) or (0-31) depending on the device.
 ******************************************************************************/
void LESENSE_DecoderStateConfig(const LESENSE_DecStDesc_TypeDef * confDecSt,
                                uint32_t decSt)
{
  /* Sanity check of configuration parameters */
  EFM_ASSERT(decSt < LESENSE_NUM_DECODER_STATES);
  EFM_ASSERT((uint32_t)confDecSt->confA.compMask < 16U);
  EFM_ASSERT((uint32_t)confDecSt->confA.compVal < 16U);
  EFM_ASSERT((uint32_t)confDecSt->confA.nextState < LESENSE_NUM_DECODER_STATES);
  EFM_ASSERT((uint32_t)confDecSt->confB.compMask < 16U);
  EFM_ASSERT((uint32_t)confDecSt->confB.compVal < 16U);
  EFM_ASSERT((uint32_t)confDecSt->confB.nextState < LESENSE_NUM_DECODER_STATES);

  /* Configure the state descriptor A (LESENSE_STi_TCONFA) for the decoder state i.
   * Setting sensor compare value, sensor mask, next state index,
   * transition action, interrupt flag option, and state descriptor chaining
   * configurations. */
  LESENSE->ST[decSt].TCONFA =
    (uint32_t)confDecSt->confA.prsAct
    | ((uint32_t)confDecSt->confA.compMask  << _LESENSE_ST_TCONFA_MASK_SHIFT)
    | ((uint32_t)confDecSt->confA.compVal   << _LESENSE_ST_TCONFA_COMP_SHIFT)
    | ((uint32_t)confDecSt->confA.nextState << _LESENSE_ST_TCONFA_NEXTSTATE_SHIFT)
    | ((uint32_t)confDecSt->confA.setInt    << _LESENSE_ST_TCONFA_SETIF_SHIFT)
    | ((uint32_t)confDecSt->chainDesc       << _LESENSE_ST_TCONFA_CHAIN_SHIFT);

  /* Configure the state descriptor Bi (LESENSE_STi_TCONFB).
   * Setting sensor compare value, sensor mask, next state index, transition
   * action, and interrupt flag option configurations. */
  LESENSE->ST[decSt].TCONFB =
    (uint32_t)confDecSt->confB.prsAct
    | ((uint32_t)confDecSt->confB.compMask  << _LESENSE_ST_TCONFB_MASK_SHIFT)
    | ((uint32_t)confDecSt->confB.compVal   << _LESENSE_ST_TCONFB_COMP_SHIFT)
    | ((uint32_t)confDecSt->confB.nextState << _LESENSE_ST_TCONFB_NEXTSTATE_SHIFT)
    | ((uint32_t)confDecSt->confB.setInt    << _LESENSE_ST_TCONFB_SETIF_SHIFT);
}

/***************************************************************************//**
 * @brief
 *   Set the LESENSE decoder state.
 *
 * @details
 *   This function can be used for setting the initial state of the LESENSE
 *   decoder.
 *
 * @note
 *   Make sure the LESENSE decoder state is initialized by this function before
 *   enabling the decoder!
 *
 * @param[in] decSt
 *   A decoder state to set as the current state. A valid range is 0-15 or 0-31,
 *   depending on the device.
 ******************************************************************************/
void LESENSE_DecoderStateSet(uint32_t decSt)
{
  EFM_ASSERT(decSt <= _LESENSE_DECSTATE_DECSTATE_MASK);

  LESENSE->DECSTATE = decSt & _LESENSE_DECSTATE_DECSTATE_MASK;
}

/***************************************************************************//**
 * @brief
 *   Get the current state of the LESENSE decoder.
 *
 * @return
 *   This function returns the value of the LESENSE_DECSTATE register that
 *   represents the current state of the LESENSE decoder.
 ******************************************************************************/
uint32_t LESENSE_DecoderStateGet(void)
{
  return LESENSE->DECSTATE & _LESENSE_DECSTATE_DECSTATE_MASK;
}

#if defined(_LESENSE_PRSCTRL_MASK)
/***************************************************************************//**
 * @brief
 *   Enable or disable the PRS output from the LESENSE decoder.
 *
 * @param[in] enable
 *   Enable/disable the PRS output from the LESENSE decoder. True to enable and
 *   false to disable.
 *
 * @param[in] decMask
 *   A decoder state compare value mask.
 *
 * @param[in] decVal
 *   A decoder state comparison value.
 ******************************************************************************/
void LESENSE_DecoderPrsOut(bool enable, uint32_t decMask, uint32_t decVal)
{
  LESENSE->PRSCTRL = (enable << _LESENSE_PRSCTRL_DECCMPEN_SHIFT)
                     | (decMask << _LESENSE_PRSCTRL_DECCMPMASK_SHIFT)
                     | (decVal << _LESENSE_PRSCTRL_DECCMPVAL_SHIFT);
}
#endif

/***************************************************************************//**
 * @brief
 *   Start scanning sensors.
 *
 * @note
 *   This function will wait for any pending previous write operation to the
 *   CMD register to complete before accessing the CMD register. It will also
 *   wait for the write operation to the CMD register to complete before
 *   returning. Each write operation to the CMD register may take up to 3 LF
 *   clock cycles, so expect some delay. The user may implement
 *   a separate function to write multiple command bits in the CMD register
 *   in one single operation to optimize an application.
 ******************************************************************************/
void LESENSE_ScanStart(void)
{
  /* Wait for any pending previous write operation to the CMD register to
     complete before accessing the CMD register. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;

  /* Start scanning sensors. */
  LESENSE->CMD = LESENSE_CMD_START;

  /* Wait for the write operation to the CMD register to complete before
     returning. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;
}

/***************************************************************************//**
 * @brief
 *   Stop scanning sensors.
 *
 * @note
 *   This function will wait for any pending previous write operation to the
 *   CMD register to complete before accessing the CMD register. It will also
 *   wait for the write operation to the CMD register to complete before
 *   returning. Each write operation to the CMD register may take up to 3 LF
 *   clock cycles, so the user should expect some delay. The user may implement
 *   a separate function to write multiple command bits in the CMD register
 *   in one single operation in order to optimize an application.
 *
 * @note
 *   If issued during a scan, the command takes effect after scan completion.
 ******************************************************************************/
void LESENSE_ScanStop(void)
{
  /* Wait for any pending previous write operation to the CMD register to
     complete before accessing the CMD register. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;

  /* Stop scanning sensors. */
  LESENSE->CMD = LESENSE_CMD_STOP;

  /* Wait for the write operation to the CMD register to complete before
     returning. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;
}

/***************************************************************************//**
 * @brief
 *   Start the LESENSE decoder.
 *
 * @note
 *   This function will wait for any pending previous write operation to the
 *   CMD register to complete before accessing the CMD register. It will also
 *   wait for the write operation to the CMD register to complete before
 *   returning. Each write operation to the CMD register may take up to 3 LF
 *   clock cycles, so expect some delay. The user may implement
 *   a separate function to write multiple command bits in the CMD register
 *   in one single operation to optimize an application.
 ******************************************************************************/
void LESENSE_DecoderStart(void)
{
  /* Wait for any pending previous write operation to the CMD register to
     complete before accessing the CMD register. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;

  /* Start the decoder. */
  LESENSE->CMD = LESENSE_CMD_DECODE;

  /* Wait for the write operation to the CMD register to complete before
     returning. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;
}

/***************************************************************************//**
 * @brief
 *   Clear the result buffer.
 *
 * @note
 *   This function will wait for any pending previous write operation to the
 *   CMD register to complete before accessing the CMD register. It will also
 *   wait for the write operation to the CMD register to complete before
 *   returning. Each write operation to the CMD register may take up to 3 LF
 *   clock cycles, so expect some delay. The user may implement
 *   a separate function to write multiple command bits in the CMD register
 *   in one single operation to optimize an application.
 ******************************************************************************/
void LESENSE_ResultBufferClear(void)
{
  /* Wait for any pending previous write operation to the CMD register to
     complete before accessing the CMD register. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;

  LESENSE->CMD = LESENSE_CMD_CLEARBUF;

  /* Wait for the write operation to the CMD register to complete before
     returning. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;
}

/***************************************************************************//**
 * @brief
 *   Reset the LESENSE module.
 *
 * @details
 *   Use this function to reset LESENSE registers.
 *
 * @note
 *   Resetting LESENSE registers is required in each reset or power-on cycle
 *   to configure the default values of the RAM mapped LESENSE registers.
 *   LESENSE_Reset() can be called on initialization by setting the @p reqReset
 *   parameter to true in LESENSE_Init().
 ******************************************************************************/
void LESENSE_Reset(void)
{
  uint32_t i;

  /* Disable all LESENSE interrupts first. */
  LESENSE->IEN = _LESENSE_IEN_RESETVALUE;

  /* Clear all pending LESENSE interrupts. */
  LESENSE->IFC = _LESENSE_IFC_MASK;

  /* Stop the decoder. */
  LESENSE->DECCTRL |= LESENSE_DECCTRL_DISABLE;

  /* Wait for any pending previous write operation to the CMD register to
     complete before accessing the CMD register. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;

  /* Stop the sensor scan and clear the result buffer. */
  LESENSE->CMD = (LESENSE_CMD_STOP | LESENSE_CMD_CLEARBUF);

  /* Reset LESENSE configuration registers */
  LESENSE->CTRL      = _LESENSE_CTRL_RESETVALUE;
  LESENSE->PERCTRL   = _LESENSE_PERCTRL_RESETVALUE;
  LESENSE->DECCTRL   = _LESENSE_DECCTRL_RESETVALUE;
  LESENSE->BIASCTRL  = _LESENSE_BIASCTRL_RESETVALUE;
#if defined(_LESENSE_EVALCTRL_MASK)
  LESENSE->EVALCTRL  = _LESENSE_EVALCTRL_RESETVALUE;
  LESENSE->PRSCTRL   = _LESENSE_PRSCTRL_RESETVALUE;
#endif
  LESENSE->CHEN      = _LESENSE_CHEN_RESETVALUE;
  LESENSE->IDLECONF  = _LESENSE_IDLECONF_RESETVALUE;
  LESENSE->ALTEXCONF = _LESENSE_ALTEXCONF_RESETVALUE;

  /* Disable LESENSE to control GPIO pins. */
#if defined(_LESENSE_ROUTE_MASK)
  LESENSE->ROUTE    = _LESENSE_ROUTE_RESETVALUE;
#else
  LESENSE->ROUTEPEN = _LESENSE_ROUTEPEN_RESETVALUE;
#endif

  /* Reset all channel configuration registers. */
  for (i = 0U; i < LESENSE_NUM_CHANNELS; ++i) {
    LESENSE->CH[i].TIMING   = _LESENSE_CH_TIMING_RESETVALUE;
    LESENSE->CH[i].INTERACT = _LESENSE_CH_INTERACT_RESETVALUE;
    LESENSE->CH[i].EVAL     = _LESENSE_CH_EVAL_RESETVALUE;
  }

  /* Reset all decoder state configuration registers. */
  for (i = 0U; i < LESENSE_NUM_DECODER_STATES; ++i) {
    LESENSE->ST[i].TCONFA = _LESENSE_ST_TCONFA_RESETVALUE;
    LESENSE->ST[i].TCONFB = _LESENSE_ST_TCONFB_RESETVALUE;
  }

  /* Wait for the write operation to the CMD register to complete before
     returning. */
  while (LESENSE_SYNCBUSY_CMD & LESENSE->SYNCBUSY)
    ;
}

/** @} (end addtogroup LESENSE) */
/** @} (end addtogroup emlib) */

#endif /* defined(LESENSE_COUNT) && (LESENSE_COUNT > 0) */
