/*!
 * \file jiffies.c
 * \brief
 *    A target independent jiffy functionality
 *
 * Author: Christos Choutouridis AEM: 8997
 * email : <cchoutou@ece.auth.gr>
 *
 */
#include "jiffies.h"

static jf_t _jf;

#define JF_MAX_TIM_VALUE      (0xFFFF)    // 16bit counters

/*
 * ======================   Public functions   ======================
 */

/*
 * Link and Glue functions
 */

/*!
 * \brief
 *    Connect the Driver's Set frequency function to jiffy struct
 * \note
 *    This function get a freq value and returns the timers max jiffy value
 *    (usual this refers to timer'sauto reload value).
 */
void jf_link_setfreq (jf_setfreq_pt pfun) {
   _jf.setfreq = (pfun != 0) ? pfun : 0;
}

/*!
 * \brief
 *    Connect the timer's value to jiffy struct
 */
void jf_link_value (jiffy_t* v) {
   _jf.value = (v != 0) ? v : 0;
}



/*
 * User Functions
 */

/*!
 * \brief
 *    Check jiffy's status
 * \return status
 */
inline drv_status_en jf_probe (void) {
   return _jf.status;
}

/*!
 * \brief
 *    De-Initialize the jf data and un-connect the functions
 *    from the driver
 */
void jf_deinit (void)
{
   if (_jf.setfreq) _jf.setfreq (0, 0);
   memset ((void*)&_jf, 0, sizeof (jf_t));
   _jf.status = DRV_NODEV;
}

/*!
 * \brief
 *    Initialize the jf to a desired jiffy frequency
 * \note
 *    This function has no effect if the inner jiffy struct
 *    is un-connected to driver. So you have to call
 *    \sa jf_connect_setfreq() and \sa jf_connect_value() first.
 * \return The status of the operation
 *    \arg  DRV_ERROR   If the init process fail
 *    \arg  DRV_NODEV   If there is no linked jiffy HW, no setfreq function
 *    \arg  DRV_READY   Success
 */
drv_status_en jf_init (uint32_t jf_freq, jiffy_t jiffies)
{
   if (_jf.setfreq) {
      _jf.status = DRV_NOINIT;
      if ( _jf.setfreq (jf_freq, jiffies) )
         return DRV_ERROR;
      _jf.jiffies = jiffies;
      _jf.freq = jf_freq;
      _jf.jp1ms = jf_per_msec ();
      _jf.jp1us = jf_per_usec ();
      _jf.jp100ns = jf_per_100nsec ();
      return _jf.status = DRV_READY;
   }
   return _jf.status = DRV_NODEV;
}

/*!
 * \brief
 *    Return the maximum jiffy value.
 */
jiffy_t jf_get_jiffies (void){
   return _jf.jiffies;
}

/*!
 * \brief
 *    Return the current jiffy value.
 * \note
 *    Usual this function returns the value of a register from a timer peripheral
 *    in the MCU. Keep in mind that its value is a moving target!
 */
jiffy_t jf_get_jiffy (void){
   return *_jf.value;
}

/*!
 * \brief
 *    Return the systems best approximation for jiffies per msec
 * \return
 *    The calculated value or zero if no calculation can apply
 *
 * \note
 *    The result tend to differ as the jiffies and freq values decreasing
 */
jiffy_t jf_per_msec (void)
{
   jiffy_t jf = (jiffy_t)(_jf.freq / 1000);
   /*            1
    * 1000Hz = -----  , Its not a magic number
    *          1msec
    */
   if (jf <= 1)      return 1;
   else              return jf;
}

/*!
 * \brief
 *    Return the systems best approximation for jiffies per usec
 * \return
 *    The calculated value or zero if no calculation can apply
 *
 * \note
 *    The result tend to differ as the jiffies and freq values decreasing
 */
jiffy_t jf_per_usec (void)
{
   jiffy_t jf = (jiffy_t)(_jf.freq / 1000000);
   /*                1
    * 1000000Hz = ------  , Its not a magic number
    *             1usec
    */
   if (jf <= 1)      return 1;
   else              return jf;
}

/*!
 * \brief
 *    Return the systems best approximation for jiffies per usec
 * \return
 *    The calculated value or zero if no calculation can apply
 *
 * \note
 *    The result tend to differ as the jiffies and freq values decreasing
 */
jiffy_t jf_per_100nsec (void)
{
   jiffy_t jf = (jiffy_t)(_jf.freq / 10000000);
   /*                 1
    * 10000000Hz = -------  , Its not a magic number
    *              100nsec
    */
   if (jf <= 1)      return 1;
   else              return jf;
}

/*!
 * \brief
 *    A code based delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 1 msec.
 * \param   msec     Time in msec for delay
 */
void jf_delay_ms (jtime_t msec)
{
   jtime_t m, m2, m1 = (jtime_t)*_jf.value;

   msec *= _jf.jp1ms;

   // Eat the time difference from msec value.
   do {
      m2 = (jtime_t)(*_jf.value);
      m = m2 - m1;
      msec -= (m>=0) ? m : _jf.jiffies + m;
      m1 = m2;
   } while (msec>0);
}

/*!
 * \brief
 *    A code based delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 1 usec.
 * \param   usec     Time in usec for delay
 */
void jf_delay_us (jtime_t usec)
{
   jtime_t m, m2, m1 = (jtime_t)*_jf.value;

   usec *= _jf.jp1us;
   if ((jtime_t)(*_jf.value) - m1 > usec) // Very small delays may return here.
      return;

   // Eat the time difference from usec value.
   do {
      m2 = (jtime_t)(*_jf.value);
      m = m2 - m1;
      usec -= (m>=0) ? m : _jf.jiffies + m;
      m1 = m2;
   } while (usec>0);
}

/*!
 * \brief
 *    A code based delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 100 nsec.
 * \param   _100nsec     Time in 100nsec for delay
 */
void jf_delay_100ns (jtime_t _100nsec)
{
   jtime_t m, m2, m1 = (jtime_t)*_jf.value;

   _100nsec *= _jf.jp100ns;
   if ((jtime_t)(*_jf.value) - m1 > _100nsec) // Very small delays may return here.
      return;

   // Eat the time difference from _100nsec value.
   do {
      m2 = (jtime_t)(*_jf.value);
      m = m2 - m1;
      _100nsec -= (m>=0) ? m : _jf.jiffies + m;
      m1 = m2;
   } while (_100nsec>0);
}

/*!
 * \brief
 *    A code based polling version delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 1 msec.
 * \param   msec     Time in msec for delay
 * \return  The status of ongoing delay
 *    \arg  0:    Delay time has passed
 *    \arg  1:    Delay is ongoing, keep calling
 */
int jf_check_msec (jtime_t msec)
{
   static jtime_t m1=-1, cnt;
   jtime_t m, m2;

   if (m1 == -1) {
      m1 = *_jf.value;
      cnt = _jf.jp1ms * msec;
   }

   // Eat the time difference from msec value.
   if (cnt>0) {
      m2 = (jtime_t)(*_jf.value);
      m = m2-m1;
      cnt -= (m>=0) ? m : _jf.jiffies + m;
      m1 = m2;
      return 1;   // wait
   }
   else {
      m1 = -1;
      return 0;   // do not wait any more
   }
}

/*!
 * \brief
 *    A code based polling version delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 1 usec.
 * \param   usec     Time in usec for delay
 * \return  The status of ongoing delay
 *    \arg  0:    Delay time has passed
 *    \arg  1:    Delay is ongoing, keep calling
 */
int jf_check_usec (jtime_t usec)
{
   static jtime_t m1=-1, cnt;
   jtime_t m, m2;

   if (m1 == -1) {
      m1 = *_jf.value;
      cnt = _jf.jp1us * usec;
   }

   // Eat the time difference from usec value.
   if (cnt>0) {
      m2 = (jtime_t)(*_jf.value);
      m = m2-m1;
      cnt -= (m>=0) ? m : _jf.jiffies + m;
      m1 = m2;
      return 1;   // wait
   }
   else {
      m1 = -1;
      return 0;   // do not wait any more
   }
}


/*!
 * \brief
 *    A code based polling version delay implementation, using jiffies for timing.
 *    This is NOT accurate but it ensures that the time passed is always
 *    more than the requested value.
 *    The delay values are multiplications of 100 nsec.
 * \param
 *    _100nsec     Time in 100nsec for delay
 * \return  The status of ongoing delay
 *    \arg  0:    Delay time has passed
 *    \arg  1:    Delay is ongoing, keep calling
 */
int jf_check_100nsec (jtime_t _100nsec)
{
   static jtime_t m1=-1, cnt;
   jtime_t m, m2;

   if (m1 == -1) {
      m1 = *_jf.value;
      cnt = _jf.jp100ns * _100nsec;
   }

   // Eat the time difference from _100nsec value.
   if (cnt>0) {
      m2 = (jtime_t)(*_jf.value);
      m = m2-m1;
      cnt -= (m>=0) ? m : _jf.jiffies + m;
      m1 = m2;
      return 1;   // wait
   }
   else {
      m1 = -1;
      return 0;   // do not wait any more
   }
}
