/***************************************************************************//**
 * @file
 * @brief HFXO Manager HAL series 2 Devices.
 *******************************************************************************
 * # License
 * <b>Copyright 2019 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_device.h"
#if defined(_SILICON_LABS_32B_SERIES_2)
#include "sl_assert.h"
#include "sl_core.h"
#include "sli_hfxo_manager.h"
#include "sl_hfxo_manager.h"
#include "sl_hfxo_manager_config.h"
#include "sl_status.h"
#include <stdbool.h>

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

#if (defined(SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT) \
  && (SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT == 1)   \
  && defined(SL_CATALOG_POWER_MANAGER_DEEPSLEEP_BLOCKING_HFXO_RESTORE_PRESENT))
#error Component power_manager_deepsleep_blocking_hfxo_restore is not compatible with SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT configuration
#endif

// Defines for hidden field FORCERAWCLK in HFXO_CTRL register
#define _HFXO_MANAGER_CTRL_FORCERAWCLK_SHIFT                  31
#define _HFXO_MANAGER_CTRL_FORCERAWCLK_MASK                   0x80000000UL
#define HFXO_MANAGER_CTRL_FORCERAWCLK                        (0x1UL << _HFXO_MANAGER_CTRL_FORCERAWCLK_SHIFT)

// Defines for hidden PKDETCTRL register
#define HFXO_MANAGER_PKDETCTRL                                (*((volatile uint32_t *)(HFXO0_BASE + 0x34UL)))
#define _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT           8
#define _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_MASK            0xF00UL
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V105MV          (0x00000000UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V132MV          (0x00000001UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V157MV          (0x00000002UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V184MV          (0x00000003UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V210MV          (0x00000004UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V236MV          (0x00000005UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V262MV          (0x00000006UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V289MV          (0x00000007UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V315MV          (0x00000008UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V341MV          (0x00000009UL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V367MV          (0x0000000AUL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V394MV          (0x0000000BUL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V420MV          (0x0000000CUL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V446MV          (0x0000000DUL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V472MV          (0x0000000EUL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)
#define HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V499MV          (0x0000000FUL << _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_SHIFT)

// IRQ Name depending on devices
#if defined(_SILICON_LABS_32B_SERIES_2_CONFIG_1)
#define HFXO_IRQ_NUMBER  HFXO00_IRQn
#define HFXO_IRQ_HANDLER_FUNCTION  HFXO00_IRQHandler
#else
#define HFXO_IRQ_NUMBER  HFXO0_IRQn
#define HFXO_IRQ_HANDLER_FUNCTION  HFXO0_IRQHandler
#endif

// Default values for the Sleepy Crystal settings
// Should be enough to guaranty HFXO startup
#define SLEEPY_XTAL_SETTING_DEFAULT_PKDETTHSTARTUPI  HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_V157MV
#define SLEEPY_XTAL_SETTING_DEFAULT_CTUNEANA         100u
#define SLEEPY_XTAL_SETTING_DEFAULT_COREBIAS         255u

/*******************************************************************************
 ***************************  LOCAL VARIABLES   ********************************
 ******************************************************************************/

// Error flag to indicate if we failed the startup process
static volatile bool error_flag = false;

#if (SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT == 1)
// Error retry counter
static volatile uint8_t error_try_cnt = 0;

// Error State status
static volatile bool in_error_state = false;

// Variables to save normal settings
static uint32_t pkdettusstartupi_saved;
static uint32_t ctunexiana_saved;
static uint32_t ctunexoana_saved;
static uint32_t corebiasana_saved;
static uint32_t corebiasstartup_saved;
static uint32_t corebiasstartupi_saved;

// Variables for Sleepy Crystal settings
static uint32_t sleepy_xtal_settings_pkdettusstartupi = SLEEPY_XTAL_SETTING_DEFAULT_PKDETTHSTARTUPI; // Value already shifted
static uint32_t sleepy_xtal_settings_ctuneana = SLEEPY_XTAL_SETTING_DEFAULT_CTUNEANA;
static uint32_t sleepy_xtal_settings_corebias = SLEEPY_XTAL_SETTING_DEFAULT_COREBIAS;
#endif

/***************************************************************************//**
 * HFXO ready notification callback for internal use with power manager
 ******************************************************************************/
__WEAK void sli_hfxo_manager_notify_ready_for_power_manager(void);

/***************************************************************************//**
 * HFXO PRS ready notification callback for internal use with power manager
 ******************************************************************************/
__WEAK void sli_hfxo_notify_ready_for_power_manager_from_prs(void);

/***************************************************************************//**
 * Hardware specific initialization.
 ******************************************************************************/
void sli_hfxo_manager_init_hardware(void)
{
  // Increase HFXO Interrupt priority so that it won't be masked by BASEPRI
  // and will preempt other interrupts.
  NVIC_SetPriority(HFXO_IRQ_NUMBER, CORE_ATOMIC_BASE_PRIORITY_LEVEL - 1);

  // Enable HFXO Interrupt if HFXO is used
#if _SILICON_LABS_32B_SERIES_2_CONFIG >= 2
  CMU->CLKEN0_SET = CMU_CLKEN0_HFXO0;
#endif

  HFXO0->IEN_CLR = HFXO_IEN_RDY | HFXO_IEN_DNSERR | HFXO_IEN_COREBIASOPTERR;
#if defined(HFXO_MANAGER_SLEEPTIMER_SYSRTC_INTEGRATION_ON)
  HFXO0->IEN_CLR = HFXO_IEN_PRSRDY;
#endif

  HFXO0->IF_CLR = HFXO_IF_RDY | HFXO_IF_DNSERR | HFXO_IEN_COREBIASOPTERR;
#if defined(HFXO_MANAGER_SLEEPTIMER_SYSRTC_INTEGRATION_ON)
  HFXO0->IF_CLR = HFXO_IF_PRSRDY;
#endif

  NVIC_ClearPendingIRQ(HFXO_IRQ_NUMBER);
  NVIC_EnableIRQ(HFXO_IRQ_NUMBER);

  HFXO0->IEN_SET = HFXO_IEN_RDY | HFXO_IEN_DNSERR | HFXO_IEN_COREBIASOPTERR;

#if defined(HFXO_MANAGER_SLEEPTIMER_SYSRTC_INTEGRATION_ON)
  HFXO0->IEN_SET = HFXO_IEN_PRSRDY;
  HFXO0->CTRL &= ~(_HFXO_CTRL_DISONDEMANDPRS_MASK & HFXO_CTRL_DISONDEMANDPRS_DEFAULT);
  HFXO0->CTRL |= HFXO_CTRL_PRSSTATUSSEL1_ENS;
#endif
}

/***************************************************************************//**
 * Updates sleepy crystal settings in specific hardware registers.
 ******************************************************************************/
sl_status_t sli_hfxo_manager_update_sleepy_xtal_settings_hardware(const sl_hfxo_manager_sleepy_xtal_settings_t *settings)
{
#if (SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT == 1)
  EFM_ASSERT(settings->ana_ctune <= (_HFXO_XTALCTRL_CTUNEXIANA_MASK >> _HFXO_XTALCTRL_CTUNEXIANA_SHIFT));
  EFM_ASSERT(settings->core_bias_current <= (_HFXO_XTALCTRL_COREBIASANA_MASK >> _HFXO_XTALCTRL_COREBIASANA_SHIFT));

  sleepy_xtal_settings_ctuneana = settings->ana_ctune;
  sleepy_xtal_settings_corebias = settings->core_bias_current;

  return SL_STATUS_OK;
#else
  (void)settings;
  return SL_STATUS_NOT_AVAILABLE;
#endif
}

/***************************************************************************//**
 * Checks if HFXO is ready and, if needed, waits for it to be.
 *
 * @note This will also make sure we are not in the process of restarting HFXO
 *       with different settings.
 ******************************************************************************/
bool sli_hfxo_manager_is_hfxo_ready(bool wait)
{
  bool ready = false;

  do {
#if defined(HFXO_MANAGER_SLEEPTIMER_SYSRTC_INTEGRATION_ON)
    ready = (((HFXO0->STATUS & (HFXO_STATUS_RDY | HFXO_STATUS_PRSRDY)) != 0) && !error_flag) ? true : false;
#else
    ready = (((HFXO0->STATUS & HFXO_STATUS_RDY) != 0) && !error_flag) ? true : false;
#endif
  } while (!ready && wait);

  return ready;
}

#if (SL_HFXO_MANAGER_CUSTOM_HFXO_IRQ_HANDLER == 0)
/*******************************************************************************
 * HFXO interrupt handler.
 *
 * @note  The HFXOx_IRQHandler provided by HFXO Manager will call
 *        @ref sl_hfxo_manager_irq_handler. Configure SL_HFXO_MANAGER_CUSTOM_HFXO_IRQ_HANDLER
 *        if the application wants to implement its own HFXOx_IRQHandler.
 ******************************************************************************/
void HFXO_IRQ_HANDLER_FUNCTION(void)
{
  sl_hfxo_manager_irq_handler();
}
#endif

/*******************************************************************************
 * HFXO Manager HFXO interrupt handler.
 ******************************************************************************/
void sl_hfxo_manager_irq_handler(void)
{
  uint32_t irq_flag = HFXO0->IF;
#if (SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT == 1)
  bool disondemand =  (HFXO0->CTRL & _HFXO_CTRL_DISONDEMAND_MASK) ? true : false;
  bool forceen = (HFXO0->CTRL & _HFXO_CTRL_FORCEEN_MASK) ? true : false;
#endif

#if defined(HFXO_MANAGER_SLEEPTIMER_SYSRTC_INTEGRATION_ON)
  if (irq_flag & HFXO_IF_PRSRDY) {
    // Clear PRS RDY flag and EM23ONDEMAND
    HFXO0->IF_CLR = irq_flag & HFXO_IF_PRSRDY;
    HFXO0->CTRL_CLR = HFXO_CTRL_EM23ONDEMAND;

    // Only retrieve start of measurement if HFXO is not already ready.
    if ((HFXO0->STATUS & HFXO_STATUS_RDY) == 0) {
      sli_hfxo_manager_retrieve_begining_startup_measurement();
    }

    // Notify power manager HFXO is ready
    sli_hfxo_notify_ready_for_power_manager_from_prs();
    sli_hfxo_manager_notify_ready_for_power_manager();

    // Update sleep on isr exit flag
    sli_sleeptimer_update_sleep_on_isr_exit(true);

    // Reset PRS signal through Sleeptimer
    sli_sleeptimer_reset_prs_signal();
  }
#endif

  // RDY Interrupt Flag Handling
  if (irq_flag & HFXO_IF_RDY) {
    // Clear Ready flag
    HFXO0->IF_CLR = irq_flag & HFXO_IF_RDY;

#if (SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT == 1)
    if (error_flag) {
      // Clear error flag, i.e. we successfully stated HFXO with the modified settings
      error_flag = false;

      // If it's the first time we succeed after an error, try back the normal settings
      if (error_try_cnt <= 1) {
        // Disable HFXO.
        HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
        HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;

        while ((HFXO0->STATUS & HFXO_STATUS_ENS) != 0) {
        }

        // Put back normal settings
        HFXO_MANAGER_PKDETCTRL = (HFXO_MANAGER_PKDETCTRL & ~_HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_MASK) | pkdettusstartupi_saved;
        HFXO0->XTALCTRL = (HFXO0->XTALCTRL & ~(_HFXO_XTALCTRL_CTUNEXIANA_MASK | _HFXO_XTALCTRL_CTUNEXOANA_MASK))
                          | ctunexiana_saved
                          | ctunexoana_saved;
        HFXO0->XTALCFG = (HFXO0->XTALCFG & ~(_HFXO_XTALCFG_COREBIASSTARTUPI_MASK | _HFXO_XTALCFG_COREBIASSTARTUP_MASK))
                         | corebiasstartup_saved
                         | corebiasstartupi_saved;
        HFXO0->XTALCTRL = (HFXO0->XTALCTRL & ~_HFXO_XTALCTRL_COREBIASANA_MASK) | corebiasana_saved;

        // Put back FORCEEN and DISONDEMAND state
        if (!disondemand) {
          HFXO0->CTRL_CLR = HFXO_CTRL_DISONDEMAND;
        } else {
          HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;
        }
        if (forceen) {
          HFXO0->CTRL_SET = HFXO_CTRL_FORCEEN;
        } else {
          HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
        }
      } else {
        // Call notification function to tell users that sleepy crystal settings are kept
        // This should only happen if you are in test condition or if you have a bad crystal.
        sl_hfxo_manager_notify_consecutive_failed_startups();
        in_error_state = true;
      }
    } else {
      sli_hfxo_manager_end_startup_measurement();

      sli_hfxo_manager_notify_ready_for_power_manager();

      // Clear counter since we successfully started HFXO with normal settings
      // or we are just keeping sleepy crystal settings indefinitely.
      error_try_cnt = 0;
    }
#else
    sli_hfxo_manager_end_startup_measurement();

    sli_hfxo_manager_notify_ready_for_power_manager();
#endif
  }

  // DNSERR Interrupt Flag Handling
  if (irq_flag & HFXO_IF_DNSERR) {
    // Clear error flag
    HFXO0->IF_CLR = irq_flag & HFXO_IF_DNSERR;

#if (SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT == 1)
    // We should not fail twice in a row
    EFM_ASSERT(error_flag == false);

    // Update global variables related to error.
    error_flag = true;
    error_try_cnt++;

    // Save current settings
    pkdettusstartupi_saved = (HFXO_MANAGER_PKDETCTRL & _HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_MASK);
    ctunexiana_saved = (HFXO0->XTALCTRL & _HFXO_XTALCTRL_CTUNEXIANA_MASK);
    ctunexoana_saved = (HFXO0->XTALCTRL & _HFXO_XTALCTRL_CTUNEXOANA_MASK);
    corebiasana_saved = (HFXO0->XTALCTRL & _HFXO_XTALCTRL_COREBIASANA_MASK);
    corebiasstartup_saved = (HFXO0->XTALCFG & _HFXO_XTALCFG_COREBIASSTARTUP_MASK);
    corebiasstartupi_saved = (HFXO0->XTALCFG & _HFXO_XTALCFG_COREBIASSTARTUPI_MASK);

    // Disable HFXO.
    HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
    HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;

    // Use FORCERAWCLK bit to exit error state when disabling
    HFXO0->CTRL_SET = HFXO_MANAGER_CTRL_FORCERAWCLK;
    while ((HFXO0->STATUS & _HFXO_STATUS_ENS_MASK) != 0U) {
    }
    HFXO0->CTRL_CLR = HFXO_MANAGER_CTRL_FORCERAWCLK;

    // Change settings:
    //Reduce Peak Detection Threshold for Startup Intermediate stage to 2 (V157MV)
    HFXO_MANAGER_PKDETCTRL = (HFXO_MANAGER_PKDETCTRL & ~_HFXO_MANAGER_PKDETCTRL_PKDETTHSTARTUPI_MASK) | sleepy_xtal_settings_pkdettusstartupi;
    // Reduce CTUNE values for steady stage
    if (((ctunexiana_saved >> _HFXO_XTALCTRL_CTUNEXIANA_SHIFT) > 100)
        || ((ctunexoana_saved >> _HFXO_XTALCTRL_CTUNEXOANA_SHIFT) > 100)) {
      HFXO0->XTALCTRL = (HFXO0->XTALCTRL & ~(_HFXO_XTALCTRL_CTUNEXIANA_MASK | _HFXO_XTALCTRL_CTUNEXOANA_MASK))
                        | (sleepy_xtal_settings_ctuneana << _HFXO_XTALCTRL_CTUNEXIANA_SHIFT)
                        | (sleepy_xtal_settings_ctuneana << _HFXO_XTALCTRL_CTUNEXOANA_SHIFT);
    }
    // Increase core bias current at all stages
    HFXO0->XTALCFG = (HFXO0->XTALCFG & ~(_HFXO_XTALCFG_COREBIASSTARTUPI_MASK | _HFXO_XTALCFG_COREBIASSTARTUP_MASK))
                     | ((sleepy_xtal_settings_corebias >> 2) << _HFXO_XTALCFG_COREBIASSTARTUPI_SHIFT)
                     | ((sleepy_xtal_settings_corebias >> 2) << _HFXO_XTALCFG_COREBIASSTARTUP_SHIFT);
    HFXO0->XTALCTRL = (HFXO0->XTALCTRL & ~_HFXO_XTALCTRL_COREBIASANA_MASK)
                      | (sleepy_xtal_settings_corebias << _HFXO_XTALCTRL_COREBIASANA_SHIFT);

    // Put back FORCEEN and DISONDEMAND state
    if (!disondemand) {
      HFXO0->CTRL_CLR = HFXO_CTRL_DISONDEMAND;
    } else {
      HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;
    }
    if (forceen) {
      HFXO0->CTRL_SET = HFXO_CTRL_FORCEEN;
    } else {
      HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
    }
#endif
  }

  if (irq_flag & HFXO_IF_COREBIASOPTERR) {
    // Clear Core Bias Optimization error flag
    HFXO0->IF_CLR = irq_flag & HFXO_IF_COREBIASOPTERR;

#if (SL_HFXO_MANAGER_SLEEPY_CRYSTAL_SUPPORT == 1)
    // In case the Core Bias Optimization fails during error handling,
    // we disable it
    if (in_error_state == true) {
      // Disable HFXO.
      HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
      HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;

      while ((HFXO0->STATUS & HFXO_STATUS_ENS) != 0) {
      }

      // Skip Core Bias Optimization in case of error
      HFXO0->XTALCTRL_SET = HFXO_XTALCTRL_SKIPCOREBIASOPT;

      // Put back FORCEEN and DISONDEMAND state
      if (!disondemand) {
        HFXO0->CTRL_CLR = HFXO_CTRL_DISONDEMAND;
      } else {
        HFXO0->CTRL_SET = HFXO_CTRL_DISONDEMAND;
      }
      if (forceen) {
        HFXO0->CTRL_SET = HFXO_CTRL_FORCEEN;
      } else {
        HFXO0->CTRL_CLR = HFXO_CTRL_FORCEEN;
      }
    }
#endif
  }
}
#endif // _SILICON_LABS_32B_SERIES_2
