/****************************************************************************
 * sched/clock/clock_perf.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 <stdint.h>

#include <nuttx/clock.h>
#include <nuttx/arch.h>
#include <nuttx/wdog.h>

#if defined(CONFIG_PERF_OVERFLOW_CORRECTION) && ULONG_MAX != UINT64_MAX

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

struct perf_s
{
  struct wdog_s wdog;
  unsigned long last;
  unsigned long overflow;
};

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

static struct perf_s g_perf;

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

/****************************************************************************
 * perf_update
 ****************************************************************************/

static void perf_update(wdparm_t arg)
{
  clock_t tick = (clock_t)LONG_MAX * TICK_PER_SEC / up_perf_getfreq();

  perf_gettime();
  wd_start((FAR struct wdog_s *)arg, tick, perf_update, arg);
}

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

/****************************************************************************
 * perf_init
 ****************************************************************************/

void perf_init(void)
{
  FAR struct perf_s *perf = &g_perf;
  clock_t tick = (clock_t)LONG_MAX * TICK_PER_SEC / up_perf_getfreq();

  perf->last = up_perf_gettime();

  /* Periodic check for overflow */

  wd_start(&perf->wdog, tick, perf_update, (wdparm_t)perf);
}

/****************************************************************************
 * perf_gettime
 ****************************************************************************/

clock_t perf_gettime(void)
{
  FAR struct perf_s *perf = &g_perf;
  unsigned long now = up_perf_gettime();

  /* Check if overflow */

  if (now < perf->last)
    {
      perf->overflow++;
    }

  perf->last = now;
  return (clock_t)now | (clock_t)perf->overflow << 32;
}

/****************************************************************************
 * perf_convert
 ****************************************************************************/

void perf_convert(clock_t elapsed, FAR struct timespec *ts)
{
  unsigned long freq = up_perf_getfreq();

  ts->tv_sec  = elapsed / freq;
  elapsed -= ts->tv_sec * freq;
  ts->tv_nsec = NSEC_PER_SEC * elapsed / freq;
}

/****************************************************************************
 * perf_getfreq
 ****************************************************************************/

unsigned long perf_getfreq(void)
{
  return up_perf_getfreq();
}

#elif defined(CONFIG_ALARM_ARCH) || defined (CONFIG_TIMER_ARCH) || \
      defined(CONFIG_ARCH_PERF_EVENTS)

/****************************************************************************
 * perf_init
 ****************************************************************************/

void perf_init(void)
{
}

/****************************************************************************
 * perf_gettime
 ****************************************************************************/

clock_t perf_gettime(void)
{
  return up_perf_gettime();
}

/****************************************************************************
 * perf_convert
 ****************************************************************************/

void perf_convert(clock_t elapsed, FAR struct timespec *ts)
{
  up_perf_convert(elapsed, ts);
}

/****************************************************************************
 * perf_getfreq
 ****************************************************************************/

unsigned long perf_getfreq(void)
{
  return up_perf_getfreq();
}

#else

/****************************************************************************
 * perf_init
 ****************************************************************************/

void perf_init(void)
{
}

/****************************************************************************
 * perf_gettime
 ****************************************************************************/

clock_t perf_gettime(void)
{
  return clock_systime_ticks();
}

/****************************************************************************
 * perf_convert
 ****************************************************************************/

void perf_convert(clock_t elapsed, FAR struct timespec *ts)
{
  clock_ticks2time(elapsed, ts);
}

/****************************************************************************
 * perf_getfreq
 ****************************************************************************/

unsigned long perf_getfreq(void)
{
  return TICK_PER_SEC;
}

#endif