#include "boards.h"

#include "nordic_common.h"
#include "nrf.h"
#include "nrf_clock.h"
#include "nrf_gpio.h"
#include "nrf_soc.h"
#include "nrf_sdm.h"
#include "nrf_nvic.h"
#include "app_util_platform.h"


static const nrf_clock_lf_cfg_t m_lfclk_config = NRF_CLOCK_LFCLKSRC;

/*
  Watchdog timeout in seconds.
*/
#define WDT_TIMEOUT     5
/*
  RTC maximal value before an overflow occurs.
*/
#define RTC_CNT_MAX     ((((uint_fast32_t)1) << RTC_BITS) - 1)

/*
  nRF52: Errata #88 WDT: Increased current consumption when configured to pause in System ON idle
    Caused by a known bug in the nRF52 chip the WDT causes a high current if it's configured to
    pause while in system ON idle. As a workaround we configure the WDT to run while sleep and
    set CC2 on RTC0 to wake up from sleep and do a COS cycle. This ensures
    that the watchdog will be feed before elapsing.
*/
#define RTC_INTERVAL     (((WDT_TIMEOUT - 1) * LFCLK_FREQUENCY) / RTC_PRESCALER)

/* prescaler selected for RTC clock */
#define RTC_PRESCALER   1
/* low frequency clock */
#define LFCLK_FREQUENCY 32768
/* define RTC counter bits */
#define RTC_BITS        24

static void nrfx_clock_anomaly_132(void);
static void init_lfclk_f(void);

int main(void)
{ 
  nrf_gpio_cfg_output(28);
  nrf_gpio_cfg_output(4);
  
  // allow wakeup from sleep on pending interrupt
  SCB->SCR |= SCB_SCR_SEVONPEND_Msk;
  
  nrf_gpio_pin_set(4);
  init_lfclk_f();
  nrf_gpio_pin_clear(4);
  
  // setup and start RTC0
  NRF_RTC0->PRESCALER = (RTC_PRESCALER - 1);
  NRF_RTC0->CC[1] = (NRF_RTC0->COUNTER + RTC_INTERVAL) & RTC_CNT_MAX;
  NRF_RTC0->INTENSET = RTC_INTENSET_COMPARE1_Msk;
  NRF_RTC0->EVTENSET = RTC_EVTEN_COMPARE1_Msk;
  NVIC_SetPriority(RTC0_IRQn, APP_IRQ_PRIORITY_LOW);
  NVIC_EnableIRQ(RTC0_IRQn);
  NRF_RTC0->TASKS_START = 1;
  
  // enable interrupts
  __ASM("CPSIE i");
  
  nrf_gpio_pin_set(28);
  
  while(1)
  {     
    // enter application wait
    __WFE();
  }
  return (0);
}

#if defined(NRF52832_XXAA) || defined(NRF52832_XXAB)

// ANOMALY 132 - LFCLK needs to avoid frame from 66us to 138us after LFCLK stop. This solution
//               applies delay of 138us before starting LFCLK.
#define ANOMALY_132_REQ_DELAY_US 138UL

// nRF52832 is clocked with 64MHz.
#define ANOMALY_132_NRF52832_FREQ_MHZ 64UL

// Convert time to cycles.
#define ANOMALY_132_DELAY_CYCLES (ANOMALY_132_REQ_DELAY_US * ANOMALY_132_NRF52832_FREQ_MHZ)

/**
 * @brief Function for applying delay of 138us before starting LFCLK.
 */
static void nrfx_clock_anomaly_132(void)
{
    uint32_t cyccnt_inital;
    uint32_t core_debug;
    uint32_t dwt_ctrl;

    // Preserve DEMCR register to do not influence into its configuration. Enable the trace and
    // debug blocks. It is required to read and write data to DWT block.
    core_debug = CoreDebug->DEMCR;
    CoreDebug->DEMCR = core_debug | CoreDebug_DEMCR_TRCENA_Msk;

    // Preserve CTRL register in DWT block to do not influence into its configuration. Make sure
    // that cycle counter is enabled.
    dwt_ctrl = DWT->CTRL;
    DWT->CTRL = dwt_ctrl | DWT_CTRL_CYCCNTENA_Msk;

    // Store start value of cycle counter.
    cyccnt_inital = DWT->CYCCNT;

    // Delay required time.
    while ((DWT->CYCCNT - cyccnt_inital) < ANOMALY_132_DELAY_CYCLES)
    {}

    // Restore preserved registers.
    DWT->CTRL = dwt_ctrl;
    CoreDebug->DEMCR = core_debug;
}

#endif // defined(NRF52832_XXAA) || defined(NRF52832_XXAB)

static void init_lfclk_f(void)
{
  nrf_gpio_cfg_output(3);
    
  // start LFCLK prior to starting by the softdevice enable to startup in wait state
  if (((NRF_CLOCK->LFCLKSTAT & CLOCK_LFCLKSTAT_STATE_Msk) >> CLOCK_LFCLKSTAT_STATE_Pos) == 0)
  {
    nrf_gpio_pin_set(3);
    // select LFCLK
    switch (m_lfclk_config.source)
    {
      case NRF_CLOCK_LF_SRC_RC:
        NRF_CLOCK->LFCLKSRC = CLOCK_LFCLKSRC_SRC_RC;
        break; // NRF_CLOCK_LF_SRC_RC
      
      case NRF_CLOCK_LF_SRC_XTAL:
        NRF_CLOCK->LFCLKSRC = CLOCK_LFCLKSRC_SRC_Xtal;
        break; // NRF_CLOCK_LF_SRC_XTAL
      
      case NRF_CLOCK_LF_SRC_SYNTH:
        NRF_CLOCK->LFCLKSRC = CLOCK_LFCLKSRC_SRC_Synth;
        break; // NRF_CLOCK_LF_SRC_SYNTH
    }
  
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    // Enable interrupt, so we can call __WFE later.
    NRF_CLOCK->INTENSET = CLOCK_INTENSET_LFCLKSTARTED_Msk;

#if defined(NRF52832_XXAA) || defined(NRF52832_XXAB)
    nrfx_clock_anomaly_132();
#endif // defined(NRF52832_XXAA) || defined(NRF52832_XXAB)
    
    NRF_CLOCK->TASKS_LFCLKSTART = 1;
    while (NRF_CLOCK->EVENTS_LFCLKSTARTED == 0)
    {
      __WFE();
    }
    NRF_CLOCK->INTENCLR = CLOCK_INTENSET_LFCLKSTARTED_Msk;
    NRF_CLOCK->EVENTS_LFCLKSTARTED = 0;
    
    nrf_gpio_pin_clear(3);
  }
}


void RTC0_IRQHandler(void)
{
  uint_fast32_t next_tick;
  static uint8_t num_invoked = 0;
    
  // COMPARE2 event ?
  if (NRF_RTC0->EVENTS_COMPARE[1] != 0)
  {
    // clear event
    NRF_RTC0->EVENTS_COMPARE[1] = 0;
    // force write operation to be done by reading the value back in Cortex M4F devices
    // URL: http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52.v1.0.0/migration/preface.html
    (void)NRF_RTC0->EVENTS_COMPARE[1];

    // calculate next tick
    next_tick = (NRF_RTC0->CC[1] + RTC_INTERVAL) & RTC_CNT_MAX;

    // set next tick
    NRF_RTC0->CC[1] = next_tick;
    
    if (++num_invoked == 3)
    {
      nrf_gpio_pin_clear(28);
      NVIC_SystemReset();
    }
  }
  
}
