/****************************************************************************
 * drivers/timers/arch_timer.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <nuttx/arch.h>
#include <nuttx/clock.h>
#include <nuttx/timers/arch_timer.h>

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#if defined(CONFIG_SCHED_TICKLESS) && defined(CONFIG_SCHED_TICKLESS_ALARM)
#  error CONFIG_SCHED_TICKLESS_ALARM must be unset to use the arch timer
#endif

#define CONFIG_BOARD_LOOPSPER100USEC ((CONFIG_BOARD_LOOPSPERMSEC+5)/10)
#define CONFIG_BOARD_LOOPSPER10USEC  ((CONFIG_BOARD_LOOPSPERMSEC+50)/100)
#define CONFIG_BOARD_LOOPSPERUSEC    ((CONFIG_BOARD_LOOPSPERMSEC+500)/1000)

/****************************************************************************
 * Private Types
 ****************************************************************************/

struct arch_timer_s
{
  FAR struct timer_lowerhalf_s *lower;
  uint32_t *next_interval;
  clock_t timebase;
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct arch_timer_s g_timer;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static inline void timespec_from_usec(FAR struct timespec *ts,
                                      uint64_t microseconds)
{
  ts->tv_sec    = microseconds / USEC_PER_SEC;
  microseconds -= (uint64_t)ts->tv_sec * USEC_PER_SEC;
  ts->tv_nsec   = microseconds * NSEC_PER_USEC;
}

#ifdef CONFIG_SCHED_TICKLESS

static uint32_t update_timeout(uint32_t timeout)
{
  struct timer_status_s status;

  /* Don't need critical section here
   * since caller already do it for us
   */

  TIMER_TICK_GETSTATUS(g_timer.lower, &status);
  if (g_timer.next_interval)
    {
      /* If the timer interrupt is in the process,
       * let the callback return the right interval.
       */

      *g_timer.next_interval = timeout;
    }
  else if (timeout != status.timeleft)
    {
      /* Otherwise, update the timeout directly. */

      TIMER_TICK_SETTIMEOUT(g_timer.lower, timeout);
      g_timer.timebase += status.timeout - status.timeleft;
    }

  return status.timeleft;
}
#endif

static uint64_t current_usec(void)
{
  struct timer_status_s status;
  clock_t timebase;

  do
    {
      timebase = g_timer.timebase;
      TIMER_GETSTATUS(g_timer.lower, &status);
    }
  while (timebase != g_timer.timebase);

  return TICK2USEC(timebase) + (status.timeout - status.timeleft);
}

static void udelay_accurate(useconds_t microseconds)
{
  uint64_t start = current_usec();
  while (current_usec() - start < microseconds)
    {
      ; /* Wait until the timeout reach */
    }
}

static void udelay_coarse(useconds_t microseconds)
{
  volatile int i;

  /* We'll do this a little at a time because we expect that the
   * CONFIG_BOARD_LOOPSPERUSEC is very inaccurate during to truncation in
   * the divisions of its calculation.  We'll use the largest values that
   * we can in order to prevent significant error buildup in the loops.
   */

  while (microseconds > 1000)
    {
      for (i = 0; i < CONFIG_BOARD_LOOPSPERMSEC; i++)
        {
        }

      microseconds -= 1000;
    }

  while (microseconds > 100)
    {
      for (i = 0; i < CONFIG_BOARD_LOOPSPER100USEC; i++)
        {
        }

      microseconds -= 100;
    }

  while (microseconds > 10)
    {
      for (i = 0; i < CONFIG_BOARD_LOOPSPER10USEC; i++)
        {
        }

      microseconds -= 10;
    }

  while (microseconds > 0)
    {
      for (i = 0; i < CONFIG_BOARD_LOOPSPERUSEC; i++)
        {
        }

      microseconds--;
    }
}

static bool timer_callback(FAR uint32_t *next_interval, FAR void *arg)
{
#ifdef CONFIG_SCHED_TICKLESS
  struct timer_status_s status;
  uint32_t temp_interval;

  g_timer.timebase     += *next_interval;
  temp_interval         = g_oneshot_maxticks;
  g_timer.next_interval = &temp_interval;
  nxsched_timer_expiration();
  g_timer.next_interval = NULL;

  TIMER_TICK_GETSTATUS(g_timer.lower, &status);
  if (temp_interval != status.timeleft)
    {
      g_timer.timebase += status.timeout - status.timeleft;
      *next_interval = temp_interval;
    }
#else
  g_timer.timebase++;
  nxsched_process_timer();
#endif

  return true;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

void up_timer_set_lowerhalf(FAR struct timer_lowerhalf_s *lower)
{
  g_timer.lower = lower;

#ifdef CONFIG_SCHED_TICKLESS
  TIMER_TICK_MAXTIMEOUT(lower, &g_oneshot_maxticks);
  TIMER_TICK_SETTIMEOUT(g_timer.lower, g_oneshot_maxticks);
#else
  TIMER_TICK_SETTIMEOUT(g_timer.lower, 1);
#endif

  TIMER_SETCALLBACK(g_timer.lower, timer_callback, NULL);
  TIMER_START(g_timer.lower);
}

/****************************************************************************
 * Name: up_timer_gettime
 *
 * Description:
 *   Return the elapsed time since power-up (or, more correctly, since
 *   the architecture-specific timer was initialized).  This function is
 *   functionally equivalent to:
 *
 *      int clock_gettime(clockid_t clockid, FAR struct timespec *ts);
 *
 *   when clockid is CLOCK_MONOTONIC.
 *
 *   This function provides the basis for reporting the current time and
 *   also is used to eliminate error build-up from small errors in interval
 *   time calculations.
 *
 *   Provided by platform-specific code and called from the RTOS base code.
 *
 * Input Parameters:
 *   ts - Provides the location in which to return the up-time.
 *
 * Returned Value:
 *   Zero (OK) is returned on success; a negated errno value is returned on
 *   any failure.
 *
 * Assumptions:
 *   Called from the normal tasking context.  The implementation must
 *   provide whatever mutual exclusion is necessary for correct operation.
 *   This can include disabling interrupts in order to assure atomic register
 *   operations.
 *
 ****************************************************************************/

#ifdef CONFIG_CLOCK_TIMEKEEPING
void weak_function up_timer_getmask(FAR clock_t *mask)
{
  uint32_t maxticks;

  TIMER_TICK_MAXTIMEOUT(g_timer.lower, &maxticks);

  *mask = 0;
  while (1)
    {
      clock_t next = (*mask << 1) | 1;
      if (next > maxticks)
        {
          break;
        }

      *mask = next;
    }
}
#endif

#if defined(CONFIG_SCHED_TICKLESS) || defined(CONFIG_CLOCK_TIMEKEEPING)
int weak_function up_timer_gettick(FAR clock_t *ticks)
{
  int ret = -EAGAIN;

  if (g_timer.lower != NULL)
    {
      *ticks = current_usec() / USEC_PER_TICK;
      ret = OK;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: up_timer_cancel
 *
 * Description:
 *   Cancel the interval timer and return the time remaining on the timer.
 *   These two steps need to be as nearly atomic as possible.
 *   nxsched_timer_expiration() will not be called unless the timer is
 *   restarted with up_timer_start().
 *
 *   If, as a race condition, the timer has already expired when this
 *   function is called, then that pending interrupt must be cleared so
 *   that up_timer_start() and the remaining time of zero should be
 *   returned.
 *
 *   NOTE: This function may execute at a high rate with no timer running (as
 *   when pre-emption is enabled and disabled).
 *
 *   Provided by platform-specific code and called from the RTOS base code.
 *
 * Input Parameters:
 *   ts - Location to return the remaining time.  Zero should be returned
 *        if the timer is not active.  ts may be zero in which case the
 *        time remaining is not returned.
 *
 * Returned Value:
 *   Zero (OK) is returned on success.  A call to up_timer_cancel() when
 *   the timer is not active should also return success; a negated errno
 *   value is returned on any failure.
 *
 * Assumptions:
 *   May be called from interrupt level handling or from the normal tasking
 *   level.  Interrupts may need to be disabled internally to assure
 *   non-reentrancy.
 *
 ****************************************************************************/

#ifdef CONFIG_SCHED_TICKLESS
int weak_function up_timer_tick_cancel(FAR clock_t *ticks)
{
  int ret = -EAGAIN;

  if (g_timer.lower != NULL)
    {
      *ticks = update_timeout(g_oneshot_maxticks);
      ret = OK;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: up_timer_start
 *
 * Description:
 *   Start the interval timer.  nxsched_timer_expiration() will be called at
 *   the completion of the timeout (unless up_timer_cancel is called to stop
 *   the timing.
 *
 *   Provided by platform-specific code and called from the RTOS base code.
 *
 * Input Parameters:
 *   ts - Provides the time interval until nxsched_timer_expiration() is
 *        called.
 *
 * Returned Value:
 *   Zero (OK) is returned on success; a negated errno value is returned on
 *   any failure.
 *
 * Assumptions:
 *   May be called from interrupt level handling or from the normal tasking
 *   level.  Interrupts may need to be disabled internally to assure
 *   non-reentrancy.
 *
 ****************************************************************************/

#ifdef CONFIG_SCHED_TICKLESS
int weak_function up_timer_tick_start(clock_t ticks)
{
  int ret = -EAGAIN;

  if (g_timer.lower != NULL)
    {
      update_timeout(ticks);
      ret = OK;
    }

  return ret;
}
#endif

/****************************************************************************
 * Name: up_perf_*
 *
 * Description:
 *   The first interface simply provides the current time value in unknown
 *   units.  NOTE:  This function may be called early before the timer has
 *   been initialized.  In that event, the function should just return a
 *   start time of zero.
 *
 *   Nothing is assumed about the units of this time value.  The following
 *   are assumed, however: (1) The time is an unsigned integer value, (2)
 *   the time is monotonically increasing, and (3) the elapsed time (also
 *   in unknown units) can be obtained by subtracting a start time from
 *   the current time.
 *
 *   The second interface simple converts an elapsed time into well known
 *   units.
 ****************************************************************************/

#ifndef CONFIG_ARCH_PERF_EVENTS
void up_perf_init(FAR void *arg)
{
  UNUSED(arg);
}

unsigned long up_perf_gettime(void)
{
  unsigned long ret = 0;

  if (g_timer.lower != NULL)
    {
      ret = current_usec();
    }

  return ret;
}

unsigned long up_perf_getfreq(void)
{
  return USEC_PER_SEC;
}

void up_perf_convert(unsigned long elapsed,
                     FAR struct timespec *ts)
{
  timespec_from_usec(ts, elapsed);
}
#endif /* CONFIG_ARCH_PERF_EVENTS */

/****************************************************************************
 * Name: up_mdelay
 *
 * Description:
 *   Delay inline for the requested number of milliseconds.
 *   *** NOT multi-tasking friendly ***
 *
 ****************************************************************************/

void weak_function up_mdelay(unsigned int milliseconds)
{
  up_udelay(USEC_PER_MSEC * milliseconds);
}

/****************************************************************************
 * Name: up_udelay
 *
 * Description:
 *   Delay inline for the requested number of microseconds.
 *
 *   *** NOT multi-tasking friendly ***
 *
 ****************************************************************************/

void weak_function up_udelay(useconds_t microseconds)
{
  if (g_timer.lower != NULL)
    {
      udelay_accurate(microseconds);
    }
  else /* Period timer hasn't been initialized yet */
    {
      udelay_coarse(microseconds);
    }
}