/*!
 * \file
 *    NUCLEO_F401RE.h
 * \brief
 *    Nucleo F401RE port file. This file contain the implementation of driver
 *    calls for F401RE board.
 *
 *  Created on: May 23, 2020
 *      Author: Christos Choutouridis AEM: 8997
 *      email : <cchoutou@ece.auth.gr>
 */
#include "NUCLEO_F401RE.h"

/*
 * =============== System ===============
 */

static clock_t volatile __ticks;       //!< CPU time
static time_t  volatile __now;         //!< Time in UNIX seconds past 1-Jan-70
static clock_t volatile __sys_freq;    //!< The CPU's time frequency (SysTick freq)

/*!
 * \brief
 *    This is the SysTick ISR, micro-system time base service for CPU time.
 * \note
 *    This service implements the SysTick callback function in order
 *    to provide micro system - os like functionalities to an application
 *    without RTOS
 */
void SysTick_Handler(void) {
   // Time
   ++__ticks;
   if ( !(__ticks % __sys_freq ) )
      ++__now; // Do not update __now when we have external time system
}


/*!
 * \brief This function configures the source of the time base.
 *        The time source is configured  to have 1ms time base with a dedicated
 *        Tick interrupt priority.
 * \param sf   Tick interrupt frequency.
 * \retval     HAL status
 */
__weak HAL_StatusTypeDef HAL_SysTick_Init(clock_t sf) {
   SystemCoreClockUpdate ();

  /* Configure the SysTick to have interrupt in sf time basis */
   if (SysTick_Config (SystemCoreClock/sf) != 0)
      return HAL_ERROR;
   __sys_freq = sf;

  /*Configure the SysTick IRQ priority */
   NVIC_SetPriority (SysTick_IRQn, 3U);

   /* Return function status */
  return HAL_OK;
}

/*!
 * Select the system frequency without calling the Setting functionality
 * \param sf   The desired value
 * \return     The desired value (enable chaining)
 */
__INLINE clock_t HAL_SelectSysTickFreq (clock_t sf){
   return __sys_freq =sf;
}

/*!
 * \brief Get the __sys_freq.
 */
__INLINE clock_t HAL_GetSysTickFreq (void){
   return __sys_freq;
}


/*!
 * \brief Reconfigure the SysTick and update __sys_freq
 * \param   sf    Tick interrupt frequency (CPU time)
 * \return  status of the operation
 *    \arg  0     Success
 *    \arg  1     Fail
 */
int HAL_SetSysTickFreq (clock_t sf) {
  /*Configure the SysTick to have interrupt in sf time basis*/
   if (__sys_freq != sf) {
      // Time base configuration
      SystemCoreClockUpdate ();
      if (SysTick_Config ( (SystemCoreClock>>3)/sf) != 0)
         return 1;
      else {
         __sys_freq = sf;
         return 0;
      }
   }
   return 0;
}

// Take over control of SysTick from HAL library

//! disable HAL_InitTick implementation
HAL_StatusTypeDef
         HAL_InitTick(uint32_t TickPriority) { return HAL_OK; }

//! Chain GetTick to our implementation
uint32_t HAL_GetTick(void) { return clock(); }

/*!
 * \brief This function provides minimum delay (in CPU time) based
 *        on variable incremented.
 * \param Delay specifies the delay time length, in CPU time.
 * \note
 *    uint32_t is implicitly convertible to clock_t and vice versa.
 */
void HAL_Delay(uint32_t Delay) {
  uint32_t tickstart = clock();

  while((clock() - tickstart) < Delay)
     ;
}

/*
 * ======== OS like Functionalities ============
 */


//! SysTick frequency getter
__INLINE clock_t get_freq (void) {
   return __sys_freq;
}

//! SysTick frequency setter
//! \return True on failure
int set_freq (clock_t sf) {
   return HAL_SetSysTickFreq (sf);
}

/*!
 * \brief
 *    determines the processor time.
 * \return
 *    the implementation's best approximation to the processor time
 *    used by the program since program invocation. The time in
 *    seconds is the value returned divided by the value of the macro
 *    CLK_TCK or CLOCKS_PER_SEC
 */
__INLINE clock_t clock (void) {
   return (clock_t) __ticks;
}

/*!
 * \brief
 *    Set the processor time used.
 * \param   c  The new CPU time value
 * \return
 *    The implementation's best approximation to the processor time
 *    used by the program since program invocation. The time in
 *    seconds is the value returned divided by the value of the macro
 *    CLK_TCK or CLOCKS_PER_SEC
 */
clock_t setclock (clock_t c) {
   return __ticks = c;
}

/*!
 * \brief
 *    determines the current calendar time. The encoding of the value is
 *    unspecified.
 * \return
 *    The implementations best approximation to the current calendar
 *    time. If timer is not a null pointer, the return value
 *    is also assigned to the object it points to.
 */
time_t time (time_t *timer) {
   if (timer)
      *timer = (time_t)__now;
   return (time_t)__now;
}

/*!
 * \brief
 *    Sets the system's idea of the time and date.  The time,
 *    pointed to by t, is measured in seconds since the Epoch, 1970-01-01
 *    00:00:00 +0000 (UTC).
 * \param   t  Pointer to new system's time and date.
 * \return     On success, zero is returned.  On error, -1 is returned
 */
int settime (const time_t *t) {
   if (t) {
      __now = *t;
      return 0;
   }
   else
      return -1;
}


/*
 * ============== Cycle count ==============
 */
/*!
 * Initialize CPU cycle measurement functionality based on DBG
 * \return    The status of the operation
 *    \arg  LLD_OK      Success
 *    \arg  LLD_ERROR   Failure
 */
LLD_Status_en CYCLE_Init (void) {
   CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;    // enable trace
   //DWT->LAR = 0xC5ACCE55;                 // <-- added unlock access to DWT (ITM, etc.)registers
   DWT->CYCCNT = 0;                       // clear DWT cycle counter
   DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;   // enable DWT cycle counter
   return LLD_OK;
}

//! CPU cycle getter
__INLINE clock_t CYCLE_Get (void) {
   return (clock_t)DWT->CYCCNT;
}

/*
 * =============== Digital I/O ===============
 * BTN  -- PC13
 * LED  -- PA5 (SB42 is in place) [SB29: PB13]
 */

//! Helper digital input pin getter
static __INLINE uint8_t _DINx (GPIO_TypeDef *port, uint32_t pin) {
   return ((port->IDR & pin) != 0) ? 1:0;
}

//! Helper digital output pin setter
static __INLINE uint8_t _DOUTx (GPIO_TypeDef *port, uint32_t pin, uint8_t st) {
   if (st) port->BSRR = (uint32_t)pin;
   else    port->BSRR = (uint32_t)pin << 16;
   return st;
}

/*!
 * Initialize GPIO port pins for Nucleo Board
 * \return     The status of the operation
 *    \arg  LLD_OK      Success
 *    \arg  LLD_ERROR   Failure
 */
LLD_Status_en Port_Init (void) {
   GPIO_InitTypeDef  GPIO_InitType;

   // Enable Port clock
   __HAL_RCC_GPIOA_CLK_ENABLE ();
   __HAL_RCC_GPIOC_CLK_ENABLE ();

   // BTN port configuration
   GPIO_InitType.Mode  = GPIO_MODE_INPUT;
   GPIO_InitType.Pin   = GPIO_PIN_13;
   GPIO_InitType.Pull  = GPIO_NOPULL;

   HAL_GPIO_Init(GPIOC, &GPIO_InitType);

   GPIO_InitType.Mode  = GPIO_MODE_OUTPUT_PP;
   GPIO_InitType.Speed = GPIO_SPEED_LOW;
   GPIO_InitType.Pin   = GPIO_PIN_5;

   HAL_GPIO_Init(GPIOA, &GPIO_InitType);

   return LLD_OK;
}

//! Nucleo's user button reader
uint8_t BTN (void) {
   return _DINx (GPIOC, GPIO_PIN_13);
}

//! Nucleo's LD2 led setter
void LED (uint8_t on) {
   _DOUTx(GPIOA, GPIO_PIN_5, on);
}

/*! Low level driver init functionality
 * \return     The status of the operation
 *    \arg  LLD_OK      Success
 *    \arg  LLD_ERROR   Failure
 */
LLD_Status_en LLD_Init (clock_t sys_freq) {
   HAL_Init();
   HAL_SysTick_Init (sys_freq);
   CYCLE_Init ();
   Port_Init ();

   return LLD_OK;
}
