/*********************************************************************
*                    SEGGER Microcontroller GmbH                     *
*                        The Embedded Experts                        *
**********************************************************************
*                                                                    *
*       (c) 1995 - 2021 SEGGER Microcontroller GmbH                  *
*                                                                    *
*       Internet: segger.com  Support: support_embos@segger.com      *
*                                                                    *
**********************************************************************
*                                                                    *
*       embOS * Real time operating system for microcontrollers      *
*                                                                    *
*       Please note:                                                 *
*                                                                    *
*       Knowledge of this file may under no circumstances            *
*       be used to write a similar product or a real-time            *
*       operating system for in-house use.                           *
*                                                                    *
*       Thank you for your fairness !                                *
*                                                                    *
**********************************************************************
*                                                                    *
*       OS version: V5.14.0.0                                        *
*                                                                    *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------
File    : RTOSInit_nRF5x.c
Purpose : Initializes and handles the hardware for embOS
*/

#include "RTOS.h"
#include "SEGGER_SYSVIEW.h"
#include "nrf.h"
#include "nrf_drv_rtc.h"
#include "nrf_drv_clock.h"

/*********************************************************************
*
*       Defines
*
**********************************************************************
*/

/*********************************************************************
*
*       System tick settings
*/
#define OS_TIMER_FREQ  (32768)
#define OS_TICK_FREQ   (1000u)
#define OS_INT_FREQ    (OS_TICK_FREQ)

/*********************************************************************
*
*       embOSView settings
*/
#ifndef   OS_VIEW_IFSELECT
  #define OS_VIEW_IFSELECT  OS_VIEW_DISABLED
#endif

#if (OS_VIEW_IFSELECT == OS_VIEW_IF_JLINK)
  #include "JLINKMEM.h"
#elif (OS_VIEW_IFSELECT == OS_VIEW_IF_UART)
  #include "BSP_UART.h"
  #define OS_UART      (0u)
  #define OS_BAUDRATE  (38400u)
#endif

/*********************************************************************
*
*       Timer and Tickless Mode
*/
//
// Counter and CC registers are 24-bit. Shift left to make signed comparison possible.
//
#define SIGNED_24BIT_CMP(a, op, b)  ((OS_I32)((a) << 8) op (OS_I32)((b) << 8))
#define RTC_MIN_COUNTER_DIFF        (2)

//
// Used to compute the RTC compare value in RTC counter cycles using the current system tick and an offset 'x'.
// Since we're using the RTC counter as an free running counter that is never stopped, we simply get the next
// time at which we want the system tick to occur with "(OS_TIME_GetTicks() + 1)" and convert them into RTC
// counter cycles. We're masking NextCompareValue, because the RTC has a 24-bit counter.
// The modulo operation clips the system time to avoid inaccurate floating point calculation due to high numbers.
// Clipping to the system time ensures that we only compute the RTC cycles within the 24-bit range, since the
// upper 8 bit are masked anyway.
//
#define COMPUTE_COMPARE_VALUE(x)    ((OS_U32)((((OS_TIME_GetTicks() + (x))) % MaxRTCTime) / CounterResolution) & 0xFFFFFFu)

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/
#if (OS_VIEW_IFSELECT == OS_VIEW_IF_JLINK)
  const OS_U32 OS_JLINKMEM_BufferSize = 32u;  // Size of the communication buffer for JLINKMEM
#else
  const OS_U32 OS_JLINKMEM_BufferSize = 0u;   // Buffer not used
#endif

/*********************************************************************
*
*       Timer and Tickless Mode
*/
static const nrfx_rtc_t m_oRtcInstance = NRFX_RTC_INSTANCE(0);  // RTC0 instance

static const double Prescaler         = 1.0;
static const double RTCFrequency      = 32768.0;
static const double CounterResolution = Prescaler / RTCFrequency * 1000;  // in ms
static const int    MaxRTCTime        = (1 << 24) * CounterResolution;    // in ms

static OS_U32 NextCompareValue = 0;
static OS_U32 PrevCompareValue = 0;

/*********************************************************************
*
*       Local functions
*
**********************************************************************
*/

#if (OS_VIEW_IFSELECT == OS_VIEW_IF_UART)
/*********************************************************************
*
*       _OS_OnRX()
*
*  Function description
*    Callback wrapper function for BSP UART module.
*/
static void _OS_OnRX(unsigned int Unit, unsigned char c) {
  OS_USE_PARA(Unit);
  OS_COM_OnRx(c);
}

/*********************************************************************
*
*       _OS_OnTX()
*
*  Function description
*    Callback wrapper function for BSP UART module.
*/
static int _OS_OnTX(unsigned int Unit) {
  OS_USE_PARA(Unit);
  return (int)OS_COM_OnTx();
}
#endif

/*********************************************************************
*
*       _OS_GetHWTimerCycles()
*
*  Function description
*    Returns the current hardware timer count value.
*
*  Return value
*    Current timer count value.
*/
static unsigned int _OS_GetHWTimerCycles(void) {
  unsigned int Counter;

  //
  // We need to calculate how many cycles has passed since the last system tick.
  //
  Counter = nrfx_rtc_counter_get(&m_oRtcInstance);
  return Counter - PrevCompareValue;
}

/*********************************************************************
*
*       _OS_GetHWTimer_IntPending()
*
*  Function description
*    Returns if the hardware timer interrupt pending flag is set.
*
*  Return value
*    == 0: Interrupt pending flag not set.
*    != 0: Interrupt pending flag set.
*/
static unsigned int _OS_GetHWTimer_IntPending(void) {
  //
  // Always return zero, because the RTC is a free running counter and
  // _OS_GetHWTimerCycles() calculates the elapsed time since the last
  // system tick interrupt.
  //
  return 0;
}

/*********************************************************************
*
*       _ConfigureNextCC()
*/
static void _ConfigureNextCC(void) {
  OS_U32 Counter;

  PrevCompareValue = COMPUTE_COMPARE_VALUE(0);
  NextCompareValue = COMPUTE_COMPARE_VALUE(1);
  //
  // RTC compare value may not be too close to counter value as it may skip the compare and don't trigger the CC event.
  //
  Counter = nrfx_rtc_counter_get(&m_oRtcInstance);
  if (SIGNED_24BIT_CMP(NextCompareValue - Counter, <, RTC_MIN_COUNTER_DIFF)) {
    NextCompareValue = nrfx_rtc_counter_get(&m_oRtcInstance) + RTC_MIN_COUNTER_DIFF;
  }

  nrfx_rtc_cc_set(&m_oRtcInstance, 0, NextCompareValue, true);  // Set the RTC compare register 0
}

/*********************************************************************
*
*       RTC0_Handler()
*
*  Function description
*    This is the hardware timer exception handler.
*/
static void RTC0_Handler(nrfx_rtc_int_type_t a_eEventType) {
  if (a_eEventType == NRFX_RTC_INT_COMPARE0) {
    OS_INT_Enter();
    OS_TICK_Handle();
    //
    // Configure the timer to generate an interrupt for the next system
    // tick only when this interrupt does not end the tickless mode,
    // because the tickless callback will already configure the timer.
    // OS_TICKLESS_IsExpired() needs to be calle after OS_TICK_Handle().
    //
    if (OS_TICKLESS_IsExpired() == 0) {
      _ConfigureNextCC();
    }
    OS_INT_Leave();
  }
}

/*********************************************************************
*
*       _EndTicklessMode()
*
*  Function description
*    This function is called after the tickless mode ended.
*    We only adjust the embOS system tick and RTC timer if the tickless
*    mode was stopped earlier. If the tickless mode elapsed, the embOS
*    system tick and RTC timer are adjusted in the RTC ISR.
*/
static void _EndTicklessMode(void) {
  if (OS_TICKLESS_IsExpired() == 0) {
    OS_U32 Counter;
    OS_U32 Time;

    Counter = nrfx_rtc_counter_get(&m_oRtcInstance);                       // Read RTC counter
    Time    = (OS_U32)((Counter - PrevCompareValue) * CounterResolution);  // Calculate elapsed time in ticks
    OS_TICKLESS_AdjustTime((OS_TIME)Time);                                 // Adjust embOS system tick
  } else {
    OS_TICKLESS_AdjustTime(OS_TICKLESS_GetPeriod());
  }
  _ConfigureNextCC();
}

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       OS_Idle()
*
*  Function description
*    This code is executed whenever no task, software timer, or
*    interrupt is ready for execution.
*
*  Additional information
*    The idle loop does not have a stack of its own, therefore no
*    functionality should be implemented that relies on the stack
*    to be preserved.
*/
void OS_Idle(void) {
  OS_U32 IdleTicks;

  OS_INT_IncDI();
  IdleTicks = OS_TICKLESS_GetNumIdleTicks();                      // Get ticks until next task or software timer needs to be scheduled
  if (IdleTicks > 1) {                                            // Enter tickless mode only if we can enter tickless mode for more than 1 tick
    if (IdleTicks > MaxRTCTime) {                                 // Make sure we don't exceed the timers maximum period
      IdleTicks = MaxRTCTime;
    }
    PrevCompareValue = COMPUTE_COMPARE_VALUE(0);
    NextCompareValue = COMPUTE_COMPARE_VALUE(IdleTicks);          // Calculate the compare match value at which the tickless mode ends.
    nrfx_rtc_cc_set(&m_oRtcInstance, 0, NextCompareValue, true);  // Set the RTC compare register 0
    OS_TICKLESS_Start(IdleTicks, _EndTicklessMode);               // Tell embOS that we have started tickless mode
  }
  OS_INT_DecRI();
  while (1) {
    __WFI();
  }
}

/*********************************************************************
*
*       OS_InitHW()
*
*  Function description
*    Initialize the hardware required for embOS to run.
*/
void OS_InitHW(void) {
  OS_INT_IncDI();
  //
  // Initialize NVIC vector table offset register if applicable for this device.
  // Might be necessary for RAM targets or application not running from 0x00.
  //
#if (defined(__VTOR_PRESENT) && __VTOR_PRESENT == 1)
  //SCB->VTOR = (OS_U32)&__Vectors;  // Don't overwrite. Application needs to use SoftDevice's vector table
#endif
  //
  // Enable instruction and data cache if applicable for this device
  //
#if (defined(__ICACHE_PRESENT) &&  __ICACHE_PRESENT == 1)
  SCB_EnableICache();
#endif
#if (defined(__DCACHE_PRESENT) &&  __DCACHE_PRESENT == 1)
  SCB_EnableDCache();
#endif
  //
  // initialize RTC0
  //
  {
    nrfx_rtc_config_t config = NRFX_RTC_DEFAULT_CONFIG;

    nrf_drv_clock_init();
    nrf_drv_clock_lfclk_request(NULL);
    config.prescaler = Prescaler - 1;
    nrfx_rtc_init(&m_oRtcInstance, &config, RTC0_Handler);
    nrfx_rtc_counter_clear(&m_oRtcInstance);
    nrfx_rtc_int_enable(&m_oRtcInstance, NRF_RTC_INT_COMPARE0_MASK);
    NextCompareValue = COMPUTE_COMPARE_VALUE(1);
    nrfx_rtc_cc_set(&m_oRtcInstance, 0, NextCompareValue, true);
    nrfx_rtc_enable(&m_oRtcInstance);
  }
  //
  // Inform embOS about the timer settings
  //
  {
    OS_SYSTIMER_CONFIG SysTimerConfig = {OS_TIMER_FREQ, OS_INT_FREQ, OS_TIMER_UPCOUNTING, _OS_GetHWTimerCycles, _OS_GetHWTimer_IntPending};
    OS_TIME_ConfigSysTimer(&SysTimerConfig);
  }
  //
  // Configure and initialize SEGGER SystemView
  //
#if (OS_SUPPORT_PROFILE != 0)
  SEGGER_SYSVIEW_Conf();
#endif
  //
  // Initialize communication for embOSView
  //
#if (OS_VIEW_IFSELECT == OS_VIEW_IF_JLINK)
  JLINKMEM_SetpfOnRx(OS_COM_OnRx);
  JLINKMEM_SetpfOnTx(OS_COM_OnTx);
  JLINKMEM_SetpfGetNextChar(OS_COM_GetNextChar);
#elif (OS_VIEW_IFSELECT == OS_VIEW_IF_UART)
  BSP_UART_Init(OS_UART, OS_BAUDRATE, BSP_UART_DATA_BITS_8, BSP_UART_PARITY_NONE, BSP_UART_STOP_BITS_1);
  BSP_UART_SetReadCallback(OS_UART, _OS_OnRX);
  BSP_UART_SetWriteCallback(OS_UART, _OS_OnTX);
#endif
  OS_INT_DecRI();
}

/*********************************************************************
*
*       Optional communication with embOSView
*
**********************************************************************
*/

/*********************************************************************
*
*       OS_COM_Send1()
*
*  Function description
*    Sends one character.
*/
void OS_COM_Send1(OS_U8 c) {
#if (OS_VIEW_IFSELECT == OS_VIEW_IF_JLINK)
  JLINKMEM_SendChar(c);
#elif (OS_VIEW_IFSELECT == OS_VIEW_IF_UART)
  BSP_UART_Write1(OS_UART, c);
#elif (OS_VIEW_IFSELECT == OS_VIEW_DISABLED)
  OS_USE_PARA(c);          // Avoid compiler warning
  OS_COM_ClearTxActive();  // Let embOS know that Tx is not busy
#endif
}

/*************************** End of file ****************************/
